1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2024-11-08 16:57:16 +01:00
SqMod/source/Signal.cpp
Sandu Liviu Catalin 41e04e5167 Initial implementation of the new event system.
Initial implementation of the new signals and slots class.
Fixed command parsing which compared a pointer to a character.
Buffer overflow fix in routines which used the limits from the entity tasks.
Switched from Sqrat::Object to Sqrat::LightObj in most places to avoid the overhead of the VM pointer.
Various other adjustments and improvements.
The plugin is currently in a broken state and crashes at shutdown. The bug is unknown at this point.
2017-02-21 21:24:59 +02:00

1717 lines
54 KiB
C++

// ------------------------------------------------------------------------------------------------
#include "Signal.hpp"
// ------------------------------------------------------------------------------------------------
namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQMODE_DECL_TYPENAME(Typename, _SC("SqSignalImpl"))
// ------------------------------------------------------------------------------------------------
Signal::SignalPool Signal::s_Signals;
Signal::FreeSignals Signal::s_FreeSignals;
/* ------------------------------------------------------------------------------------------------
* Class used to control the signal emitter.
*/
struct SignalWrapper
{
// --------------------------------------------------------------------------------------------
using Slot = Signal::Slot;
// --------------------------------------------------------------------------------------------
Signal * mSignal; // A raw pointer to the signal instance.
// --------------------------------------------------------------------------------------------
Slot mSlot; // The specified slot.
HSQUIRRELVM mVM; // The specified virtual machine.
SQInteger mRes; // The result of the operation.
bool mOne; // Limit to one slot in the operation.
bool mAppend; // Append instead of push when leading or tailing.
/* --------------------------------------------------------------------------------------------
* Explicit constructor.
*/
SignalWrapper(HSQUIRRELVM vm, bool extra = false)
: mSignal(nullptr)
, mSlot()
, mVM(vm)
, mRes(Initialize(vm, extra))
{
//...
}
/* --------------------------------------------------------------------------------------------
* Extracts the information from the stack of the specified virtual machine.
*/
SQInteger Initialize(HSQUIRRELVM vm, bool extra)
{
const SQInteger top = sq_gettop(vm);
// See if the minimum amount of arguments were specified
if (top <= 1)
{
return sq_throwerror(vm, "Wrong number of parameters");
}
// Attempt to extract the signal instance
try
{
mSignal = Var< Signal * >(vm, 1).value;
}
catch (const Sqrat::Exception & e)
{
return sq_throwerror(vm, e.what());
}
// Do we have a valid signal instance?
if (!mSignal)
{
return sq_throwerror(vm, "Invalid signal instance");
}
// By default, extra parameters start after callback
SQInteger extpos = 3;
// Is the first parameter a function?
if (sq_gettype(vm, 2) & (_RT_CLOSURE | _RT_NATIVECLOSURE))
{
// Attempt to grab the callback object
SQRESULT res = sq_getstackobj(vm, 2, &mSlot.mFuncRef);
// Did we fail to retrieve the callback object?
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
else
{
sq_addref(vm, &mSlot.mFuncRef);
}
// Grab the hash of the callback object
mSlot.mFuncHash = sq_gethash(vm, 2);
// Push the root table on the stack
sq_pushroottable(vm);
// Attempt to grab root table as the environment
res = sq_getstackobj(vm, -1, &mSlot.mThisRef);
// Did we fail to retrieve the root table?
if (SQ_FAILED(res))
{
// Pop the root table from the stack
sq_pop(vm, 1);
// Propagate the error
return res;
}
else
{
sq_addref(vm, &mSlot.mThisRef);
}
// Grab the hash of the root table
mSlot.mThisHash = sq_gethash(vm, -1);
// Pop the root table from the stack
sq_pop(vm, 1);
}
// Should we look for a specific environment?
else if (top >= 3 && sq_gettype(vm, 3) & (_RT_CLOSURE | _RT_NATIVECLOSURE))
{
// Is the first type at suitable to be an environment at least?
if (!(sq_gettype(vm, 2) & (_RT_TABLE | _RT_CLASS | _RT_INSTANCE)))
{
return sq_throwerror(vm, "Invalid environment object");
}
// Attempt to grab the environment object
SQRESULT res = sq_getstackobj(vm, 2, &mSlot.mThisRef);
// Did we fail to retrieve the environment object?
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
else
{
sq_addref(vm, &mSlot.mThisRef);
}
// Grab the hash of the environment object
mSlot.mThisHash = sq_gethash(vm, 2);
// Attempt to grab the callback object
res = sq_getstackobj(vm, 3, &mSlot.mFuncRef);
// Did we fail to retrieve the callback object?
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
else
{
sq_addref(vm, &mSlot.mFuncRef);
}
// Grab the hash of the callback object
mSlot.mFuncHash = sq_gethash(vm, 3);
// The extra parameters start one slot higher
++extpos;
}
else
{
return sq_throwerror(vm, "Missing callback function");
}
// Should we look for the extra parameters?
if (extra && top >= extpos)
{
SQBool value;
// Attempt to retrieve the parameter value
sq_tobool(vm, extpos, &value);
// Convert the retrieved value
mOne = static_cast< bool >(value);
// The next extra parameter starts one slot higher
++extpos;
}
else
{
mOne = false;
}
// Should we look for the extra parameters?
if (extra && top >= extpos)
{
SQBool value;
// Attempt to retrieve the parameter value
sq_tobool(vm, extpos, &value);
// Convert the retrieved value
mAppend = static_cast< bool >(value);
}
else
{
mAppend = true;
}
// Initialization was successful
return SQ_OK;
}
/* --------------------------------------------------------------------------------------------
* Copy constructor. (disabled)
*/
SignalWrapper(const SignalWrapper & o) = delete;
/* --------------------------------------------------------------------------------------------
* Move constructor. (disabled)
*/
SignalWrapper(SignalWrapper && o) = delete;
/* --------------------------------------------------------------------------------------------
* Copy assignment operator. (disabled)
*/
SignalWrapper & operator = (const SignalWrapper & o) = delete;
/* --------------------------------------------------------------------------------------------
* Move assignment operator. (disabled)
*/
SignalWrapper & operator = (SignalWrapper && o) = delete;
};
/* ------------------------------------------------------------------------------------------------
* Helper functor to locate specific slots.
*/
template < class Slot > struct MatchSlot
{
// --------------------------------------------------------------------------------------------
const SQHash mThisHash; // The environment to search for.
const SQHash mFuncHash; // The callback to search for.
/* --------------------------------------------------------------------------------------------
* Base constructor.
*/
MatchSlot(SQHash t, SQHash f)
: mThisHash(t), mFuncHash(f)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Function call operator.
*/
inline bool operator () (const Slot & s) const
{
return (mThisHash == s.mThisHash) && (mFuncHash == s.mFuncHash);
}
};
/* ------------------------------------------------------------------------------------------------
* Helper functor to locate slots with specific environments.
*/
template < class Slot > struct MatchThis
{
// --------------------------------------------------------------------------------------------
const SQHash mThisHash; // The environment to search for.
/* --------------------------------------------------------------------------------------------
* Base constructor.
*/
MatchThis(SQHash t)
: mThisHash(t)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Function call operator.
*/
inline bool operator () (const Slot & s) const
{
return (mThisHash == s.mThisHash);
}
};
/* ------------------------------------------------------------------------------------------------
* Helper functor to locate slots with specific callbacks.
*/
template < class Slot > struct MatchFunc
{
// --------------------------------------------------------------------------------------------
const SQHash mFuncHash; // The callback to search for.
/* --------------------------------------------------------------------------------------------
* Base constructor.
*/
MatchFunc(SQHash f)
: mFuncHash(f)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Function call operator.
*/
inline bool operator () (const Slot & s) const
{
return (mFuncHash == s.mFuncHash);
}
};
/* ------------------------------------------------------------------------------------------------
* See if a certain slot exists using the provided functor.
*/
template < typename F, class Slot > static bool ExistsIf(F func, Slot * itr, Slot * end)
{
// Process slots within the given range
while (itr != end)
{
// Does this slot satisfy the functor?
if (func(*(itr++)))
{
return true;
}
}
return false;
}
/* ------------------------------------------------------------------------------------------------
* Count the number of slots that the provided functor deems worthy.
*/
template < typename F, class Slot > static Signal::SizeType CountIf(F func, Slot * itr, Slot * end)
{
Signal::SizeType count = 0;
// Process slots within the given range
while (itr != end)
{
// Should we count this slot?
if (func(*(itr++)))
{
++count;
}
}
// Return the final count
return count;
}
/* ------------------------------------------------------------------------------------------------
* Remove all slot from the specified range if the provided functor demands it.
*/
template < typename F, class Slot, class Scope >
static Signal::SizeType RemoveIf(F func, Slot * itr, Slot * end, Scope * scope)
{
Slot * dest = itr;
// Number of removed slots
Signal::SizeType count = 0;
// Process slots within the given range
while (itr != end)
{
// Should we remove this slot?
if (func(*itr))
{
// Release the stored references
itr->Release();
// Are we currently signaling?
if (scope != nullptr)
{
scope->Descend(itr); // Update iterators
}
// Increase the counter
++count;
}
// Were there any slots removed?
else if (itr != dest)
{
// Offset the current scope
*dest = std::move(*itr);
// Advance the destination
++dest;
}
else
{
++dest; // We avoided a move assignment
}
// Advance the iterator
++itr;
}
// Return the final count
return count;
}
/* ------------------------------------------------------------------------------------------------
* Move to the front all slots that the provided functor demands.
*/
template < typename F, class Slot, class Scope >
static void LeadIf(F func, Slot * front, Slot * end, bool one, bool append, Scope * scope)
{
Slot * itr = front;
// Process slots within the given range
while (itr != end)
{
// Should this slot become a lead?
if (!func(*itr))
{
++itr;
// Skip it
continue;
}
// Is this slot right behind the current lead?
else if ((itr - front) == 1)
{
// Swap them
itr->Swap(*front);
// Are we currently signaling?
if (scope != nullptr)
{
scope->Lead(itr); // Update iterators
}
}
// Is this not the current lead?
else if (itr != front)
{
// Backup the values of this slot
Slot tmp(std::move(*itr));
// Shift back all the slots before it
for (Slot * dest = itr, * src = (itr - 1); dest != front; dest = src, --src)
{
dest->Swap(*src); // Swap with the one bellow it
}
// Finally, place the slot into lead
*front = std::move(tmp);
// Are we currently signaling?
if (scope != nullptr)
{
scope->Lead(itr); // Update iterators
}
}
// Advance the iterator
++itr;
// Are we using the append method?
if (append)
{
++front; // Don't overrule the current lead
}
// Should we make only one lead?
if (one)
{
break; // Then stop here!
}
}
}
/* ------------------------------------------------------------------------------------------------
* Move to the back all slots that the provided functor demands.
*/
template < typename F, class Slot, class Scope >
static void TailIf(F func, Slot * front, Slot * back, bool one, bool append, Scope * scope)
{
Slot * itr = back;
// Process slots within the given range
while (itr >= front)
{
// Should this slot become a tail?
if (!func(*itr))
{
--itr;
// Skip it
continue;
}
// Is this slot right behind the current tail?
else if ((back - itr) == 1)
{
// Swap them
itr->Swap(*front);
// Are we currently signaling?
if (scope != nullptr)
{
scope->Tail(itr); // Update iterators
}
}
// Is this not the current tail?
else if (itr != back)
{
// Backup the values of this slot
Slot tmp(std::move(*itr));
// Shift up all the slots before it
for (Slot * dest = itr, * src = (itr + 1); dest != back; dest = src, ++src)
{
dest->Swap(*src); // Swap with the one above it
}
// Finally, place the slot into tail
*back = std::move(tmp);
// Are we currently signaling?
if (scope != nullptr)
{
scope->Tail(itr); // Update iterators
}
}
// Advance the iterator
--itr;
// Are we using the append method?
if (append)
{
--back; // Don't overrule the current tail
}
// Should we make only one tail?
if (one)
{
break; // Then stop here!
}
}
}
// ------------------------------------------------------------------------------------------------
void Signal::Scope::Descend(Pointer ptr)
{
// Is the descended pointer withing this scope?
if (ptr <= mEnd)
{
// Is the iterator affected by this?
if (ptr < mItr || mItr > mEnd)
{
--mItr;
}
// Update the end iterator
--mEnd;
}
// Is this that last scope?
if (mChild != nullptr)
{
mChild->Descend(ptr); // Let the others know as well
}
}
// ------------------------------------------------------------------------------------------------
void Signal::Scope::Lead(Pointer ptr)
{
// Is the descended pointer in our scope?
if (ptr <= mEnd)
{
// Does it affect our iterator?
if (ptr >= mItr && mItr != mEnd)
{
++mItr;
}
}
// Is the descended pointer out of our scope?
else if (ptr > mEnd)
{
++mItr, ++mEnd;
}
// Is this that last scope?
if (mChild != nullptr)
{
mChild->Lead(ptr); // Let the others know as well
}
}
// ------------------------------------------------------------------------------------------------
void Signal::Scope::Tail(Pointer ptr)
{
// Is the descended pointer in our scope?
if (ptr <= mEnd)
{
// Did this scope already finish processing?
if (mItr != mEnd)
{
// Does it affect our iterator?
if (ptr < mItr)
{
--mItr;
}
// Update the end iterator
--mEnd;
}
}
// Is this that last scope?
if (mChild != nullptr)
{
mChild->Tail(ptr); // Let the others know as well
}
}
// ------------------------------------------------------------------------------------------------
void Signal::Scope::Finish()
{
// Forcefully skip all remaining slots
mItr = mEnd;
// Is this that last scope?
if (mChild != nullptr)
{
mChild->Finish(); // Let the others know as well
}
}
// ------------------------------------------------------------------------------------------------
Signal::Signal()
: m_Used(0)
, m_Size(SMB_SIZE)
, m_Slots(m_SMB)
, m_Scope(nullptr)
, m_Name()
, m_Data()
{
s_FreeSignals.push_back(this);
}
// ------------------------------------------------------------------------------------------------
Signal::Signal(String && name)
: m_Used(0)
, m_Size(SMB_SIZE)
, m_Slots(m_SMB)
, m_Scope(nullptr)
, m_Name(std::forward< String >(name))
, m_Data()
{
if (m_Name.empty())
{
s_FreeSignals.push_back(this);
}
}
// ------------------------------------------------------------------------------------------------
Signal::~Signal()
{
ClearSlots();
// Should we erase this from the fee signals list?
if (m_Name.empty())
{
s_FreeSignals.erase(std::remove(s_FreeSignals.begin(), s_FreeSignals.end(), this),
s_FreeSignals.end());
}
}
// ------------------------------------------------------------------------------------------------
bool Signal::AdjustSlots(SizeType capacity)
{
// Is it necessary to resize?
if (capacity <= m_Size)
{
return true; // Already have that memory available!
}
// Do not alter the current capacity
SizeType size = m_Size;
// Calculate the next optimal size of the buffer
while (size < capacity)
{
size += (size + 1) >> 1;
}
// Attempt to allocate a memory buffer of the resulted size
Pointer slots = reinterpret_cast< Pointer >(new uint8_t[size * sizeof(Slot)]);
// See if the memory could be allocated
if (slots == nullptr)
{
return false; // Unable to acquire the memory!
}
// Do not alter the pointer to the new buffer
Pointer dest = slots;
// Are there any existing slots?
if (m_Used)
{
// Grab the range of slots to be transfered
Pointer src = m_Slots, end = (m_Slots + m_Used);
// Transfer the existing slots
while (src != end)
{
// Transfer to the new buffer
new (dest++) Slot(std::move(*(src)));
// Destroy the old instance
(src++)->~Slot();
}
// Grab the end of the remaining slots
end = (m_Slots + m_Size);
// Destroy the remaining slots
while (src != end)
{
(src++)->~Slot();
}
}
// Grab the end of the new buffer
Pointer end = (slots + size);
// Initialize the remaining slots
while (dest != end)
{
new (dest++) Slot();
}
// Update the iterators from current scopes
for (Scope * scope = m_Scope; scope != nullptr; scope = scope->mChild)
{
scope->mItr = slots + (scope->mItr - m_Slots);
scope->mEnd = slots + (scope->mEnd - m_Slots);
}
// Should we delete the current buffer?
if (m_Slots != m_SMB)
{
delete[] reinterpret_cast< uint8_t * >(m_Slots);
}
// Assign the new buffer
m_Slots = slots;
// Assign the new capacity
m_Size = size;
// The buffer was successfully adjusted
return true;
}
// ------------------------------------------------------------------------------------------------
void Signal::ClearSlots()
{
// Release every connected slot
for (Pointer itr = m_Slots, end = m_Slots + m_Used; itr != end; ++itr)
{
itr->Release();
}
// Are we currently signaling?
if (m_Scope != nullptr)
{
m_Scope->Finish(); // Update iterators
}
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Connect(SignalWrapper & w)
{
// Make sure we have enough space to store the slot
if ((m_Used < m_Size) || AdjustSlots(m_Used + 1))
{
m_Slots[m_Used++].Swap(w.mSlot); // Connect the slot to the signal
}
else
{
sq_throwerror(w.mVM, "Unable to acquire enough memory");
}
// Specify that we don't return a value
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::ConnectOnce(SignalWrapper & w)
{
// Disconnect every occurrence of this slot first
w.mRes = Eliminate(w);
// Did we fail to disconnect it?
if (SQ_FAILED(w.mRes))
{
return w.mRes; // Propagate the error
}
// Finally, attempt to connect it again
return Connect(w);
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal:: Disconnect(SignalWrapper & w)
{
return Eliminate(w);
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Exists(SignalWrapper & w)
{
// Forward the call to the actual function
const bool r = ExistsIf(MatchSlot< Slot >(w.mSlot.mThisHash, w.mSlot.mFuncHash),
m_Slots, m_Slots + m_Used);
// Push the resulted value on the stack
sq_pushbool(w.mVM, r);
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::ExistsThis(SignalWrapper & w)
{
// Forward the call to the actual function
const bool r = ExistsIf(MatchThis< Slot >(w.mSlot.mThisHash), m_Slots, m_Slots + m_Used);
// Push the resulted value on the stack
sq_pushbool(w.mVM, r);
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::ExistsFunc(SignalWrapper & w)
{
// Forward the call to the actual function
const bool r = ExistsIf(MatchFunc< Slot >(w.mSlot.mFuncHash), m_Slots, m_Slots + m_Used);
// Push the resulted value on the stack
sq_pushbool(w.mVM, r);
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Count(SignalWrapper & w)
{
// Forward the call to the actual function
const SizeType r = CountIf(MatchSlot< Slot >(w.mSlot.mThisHash, w.mSlot.mFuncHash),
m_Slots, m_Slots + m_Used);
// Push the resulted value on the stack
sq_pushinteger(w.mVM, static_cast< SQInteger >(r));
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::CountThis(SignalWrapper & w)
{
// Forward the call to the actual function
const SizeType r = CountIf(MatchThis< Slot >(w.mSlot.mThisHash), m_Slots, m_Slots + m_Used);
// Push the resulted value on the stack
sq_pushinteger(w.mVM, static_cast< SQInteger >(r));
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::CountFunc(SignalWrapper & w)
{
// Forward the call to the actual function
const SizeType r = CountIf(MatchFunc< Slot >(w.mSlot.mFuncHash), m_Slots, m_Slots + m_Used);
// Push the resulted value on the stack
sq_pushinteger(w.mVM, static_cast< SQInteger >(r));
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Lead(SignalWrapper & w)
{
// Make sure that there's more than one slot connected
if (m_Used > 1)
{
LeadIf(MatchSlot< Slot >(w.mSlot.mThisHash, w.mSlot.mFuncHash),
m_Slots, m_Slots + m_Used, w.mOne, w.mAppend, m_Scope);
}
// Specify that we don't return a value
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::LeadThis(SignalWrapper & w)
{
// Make sure that there's more than one slot connected
if (m_Used > 1)
{
LeadIf(MatchThis< Slot >(w.mSlot.mThisHash),
m_Slots, m_Slots + m_Used, w.mOne, w.mAppend, m_Scope);
}
// Specify that we don't return a value
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::LeadFunc(SignalWrapper & w)
{
// Make sure that there's more than one slot connected
if (m_Used > 1)
{
LeadIf(MatchFunc< Slot >(w.mSlot.mFuncHash),
m_Slots, m_Slots + m_Used, w.mOne, w.mAppend, m_Scope);
}
// Specify that we don't return a value
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Tail(SignalWrapper & w)
{
// Make sure that there's more than one slot connected
if (m_Used > 1)
{
TailIf(MatchSlot< Slot >(w.mSlot.mThisHash, w.mSlot.mFuncHash),
m_Slots, m_Slots + m_Used, w.mOne, w.mAppend, m_Scope);
}
// Specify that we don't return a value
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::TailThis(SignalWrapper & w)
{
// Make sure that there's more than one slot connected
if (m_Used > 1)
{
TailIf(MatchThis< Slot >(w.mSlot.mThisHash),
m_Slots, m_Slots + m_Used, w.mOne, w.mAppend, m_Scope);
}
// Specify that we don't return a value
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::TailFunc(SignalWrapper & w)
{
// Make sure that there's more than one slot connected
if (m_Used > 1)
{
TailIf(MatchFunc< Slot >(w.mSlot.mFuncHash),
m_Slots, m_Slots + m_Used, w.mOne, w.mAppend, m_Scope);
}
// Specify that we don't return a value
return 0;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Eliminate(SignalWrapper & w)
{
// Make sure that there's at least one slot connected
if (m_Used != 0)
{
// Backup the current number of used slots
const SizeType count = m_Used;
// Forward the call to the actual function
RemoveIf(MatchSlot< Slot >(w.mSlot.mThisHash, w.mSlot.mFuncHash),
m_Slots, m_Slots + m_Used, m_Scope);
// Push the number of removed slots
sq_pushinteger(w.mVM, static_cast< SQInteger >(count - m_Used));
}
else
{
sq_pushinteger(w.mVM, 0);
}
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::EliminateThis(SignalWrapper & w)
{
// Make sure that there's at least one slot connected
if (m_Used != 0)
{
// Backup the current number of used slots
const SizeType count = m_Used;
// Forward the call to the actual function
RemoveIf(MatchThis< Slot >(w.mSlot.mThisHash), m_Slots, m_Slots + m_Used, m_Scope);
// Push the number of removed slots
sq_pushinteger(w.mVM, static_cast< SQInteger >(count - m_Used));
}
else
{
sq_pushinteger(w.mVM, 0);
}
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::EliminateFunc(SignalWrapper & w)
{
// Make sure that there's at least one slot connected
if (m_Used != 0)
{
// Backup the current number of used slots
const SizeType count = m_Used;
// Forward the call to the actual function
RemoveIf(MatchFunc< Slot >(w.mSlot.mFuncHash), m_Slots, m_Slots + m_Used, m_Scope);
// Push the number of removed slots
sq_pushinteger(w.mVM, static_cast< SQInteger >(count - m_Used));
}
else
{
sq_pushinteger(w.mVM, 0);
}
// Specify that we returned a value
return 1;
}
// ------------------------------------------------------------------------------------------------
#define SQMOD_SIGNAL_CONTROL_WRAPPER(_f, _e) /*
*/ SQInteger Signal::Sq##_f(HSQUIRRELVM vm) { /*
*/ SignalWrapper w(vm, _e); /*
*/ if (SQ_FAILED(w.mRes)) return w.mRes; /*
*/ else return w.mSignal->_f(w); /*
*/ } /*
*/ /*
*/
// ------------------------------------------------------------------------------------------------
SQMOD_SIGNAL_CONTROL_WRAPPER(Connect, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(ConnectOnce, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(Exists, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(Disconnect, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(ExistsThis, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(ExistsFunc, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(Count, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(CountThis, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(CountFunc, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(Lead, true)
SQMOD_SIGNAL_CONTROL_WRAPPER(LeadThis, true)
SQMOD_SIGNAL_CONTROL_WRAPPER(LeadFunc, true)
SQMOD_SIGNAL_CONTROL_WRAPPER(Tail, true)
SQMOD_SIGNAL_CONTROL_WRAPPER(TailThis, true)
SQMOD_SIGNAL_CONTROL_WRAPPER(TailFunc, true)
SQMOD_SIGNAL_CONTROL_WRAPPER(Eliminate, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(EliminateThis, false)
SQMOD_SIGNAL_CONTROL_WRAPPER(EliminateFunc, false)
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Emit(HSQUIRRELVM vm, SQInteger top)
{
// Are there any slots connected?
if (!m_Used) return 0;
// Enter a new execution scope
Scope scope(m_Scope, m_Slots, m_Slots + m_Used);
// Activate the current scope and create a guard to restore it
const AutoAssign< Scope * > aa(m_Scope, scope.mParent, &scope);
// Contains the last received result
SQRESULT res = SQ_OK;
// Process the slots from this scope
while (scope.mItr != scope.mEnd)
{
// Grab a reference to the current slot
const Slot & slot = *(scope.mItr++);
// Push the callback object
sq_pushobject(vm, slot.mFuncRef);
// Is there an explicit environment?
if (slot.mThisHash == 0)
{
sq_pushroottable(vm);
}
else
{
sq_pushobject(vm, slot.mThisRef);
}
// Are there any parameters to forward?
if (top > 1)
{
for (SQInteger i = 2; i <= top; ++i)
{
sq_push(vm, i);
}
}
// Make the function call and store the result
res = sq_call(vm, top, false, ErrorHandling::IsEnabled());
// Pop the callback object from the stack
sq_pop(vm, 1);
// Validate the result
if (SQ_FAILED(res))
{
break; // Stop emitting signals
}
}
// Return the last result
return res;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Query(HSQUIRRELVM vm, SQInteger top)
{
// Are there any slots connected?
if (!m_Used) return 0;
// The collector and the specified environment
HSQOBJECT cthis, cfunc;
// Attempt to grab the collector environment
SQRESULT res = sq_getstackobj(vm, 2, &cthis);
// Validate the result
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
// Was there a valid environment?
else if (sq_isnull(cthis))
{
// Default to the root table
sq_pushroottable(vm);
// Try to grab the collector environment again
res = sq_getstackobj(vm, -1, &cthis);
// Pop the root table from the stack
sq_pop(vm, 1);
// Validate the result
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
}
// Grab the collector callback
res = sq_getstackobj(vm, 3, &cfunc);
// Validate the result
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
// Some dummy checks to make sure the collector is a callable object
else if (!(sq_type(cfunc) & (_RT_CLOSURE | _RT_NATIVECLOSURE)))
{
return sq_throwerror(vm, "Invalid collector callback");
}
// Enter a new execution scope
Scope scope(m_Scope, m_Slots, m_Slots + m_Used);
// Activate the current scope and create a guard to restore it
const AutoAssign< Scope * > aa(m_Scope, scope.mParent, &scope);
// Process the slots from this scope
while (scope.mItr != scope.mEnd)
{
// Grab a reference to the current slot
const Slot & slot = *(scope.mItr++);
// Push the callback object
sq_pushobject(vm, slot.mFuncRef);
// Is there an explicit environment?
if (slot.mThisHash == 0)
{
sq_pushroottable(vm);
}
else
{
sq_pushobject(vm, slot.mThisRef);
}
// Are there any parameters to forward?
if (top > 3)
{
for (SQInteger i = 4; i <= top; ++i)
{
sq_push(vm, i);
}
}
// Make the function call and store the result
res = sq_call(vm, top-2, true, ErrorHandling::IsEnabled());
// Validate the result
if (SQ_FAILED(res))
{
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
// Stop emitting signals
break;
}
// Push the collector onto the stack
sq_pushobject(vm, cfunc);
sq_pushobject(vm, cthis);
// Push the returned value
sq_push(vm, -3);
// Make the function call and store the result
res = sq_call(vm, 2, false, ErrorHandling::IsEnabled());
// Pop the callback object, return value and collector from the stack
sq_pop(vm, 3);
// Validate the result
if (SQ_FAILED(res))
{
break; // Stop emitting signals
}
}
// Return the last result
return res;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Consume(HSQUIRRELVM vm, SQInteger top)
{
// Are there any slots connected?
if (!m_Used) return 0;
// Enter a new execution scope
Scope scope(m_Scope, m_Slots, m_Slots + m_Used);
// Activate the current scope and create a guard to restore it
const AutoAssign< Scope * > aa(m_Scope, scope.mParent, &scope);
// Contains the last received result
SQRESULT res = SQ_OK;
// Default to not consumed
SQBool ret = SQFalse;
// Process the slots from this scope
while (scope.mItr != scope.mEnd)
{
// Grab a reference to the current slot
const Slot & slot = *(scope.mItr++);
// Push the callback object
sq_pushobject(vm, slot.mFuncRef);
// Is there an explicit environment?
if (slot.mThisHash == 0)
{
sq_pushroottable(vm);
}
else
{
sq_pushobject(vm, slot.mThisRef);
}
// Are there any parameters to forward?
if (top > 1)
{
for (SQInteger i = 2; i <= top; ++i)
{
sq_push(vm, i);
}
}
// Make the function call and store the result
res = sq_call(vm, top, true, ErrorHandling::IsEnabled());
// Validate the result
if (SQ_FAILED(res))
{
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
// Stop emitting signals
break;
}
// Is the returned value not null?
else if (sq_gettype(vm, -1) != OT_NULL)
{
// Obtain the returned value
sq_tobool(vm, -1, &ret);
// Should we proceed to the next slot or stop here?
if (ret == SQTrue)
{
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
// The slot consumed the signal
break;
}
}
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
}
// Did we fail to process slots?
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
// Forward the returned value to the invoker
sq_pushbool(vm, ret);
// Specify that we returned something
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Approve(HSQUIRRELVM vm, SQInteger top)
{
// Are there any slots connected?
if (!m_Used) return 0;
// Enter a new execution scope
Scope scope(m_Scope, m_Slots, m_Slots + m_Used);
// Activate the current scope and create a guard to restore it
const AutoAssign< Scope * > aa(m_Scope, scope.mParent, &scope);
// Contains the last received result
SQRESULT res = SQ_OK;
// Default to approved
SQBool ret = SQTrue;
// Process the slots from this scope
while (scope.mItr != scope.mEnd)
{
// Grab a reference to the current slot
const Slot & slot = *(scope.mItr++);
// Push the callback object
sq_pushobject(vm, slot.mFuncRef);
// Is there an explicit environment?
if (slot.mThisHash == 0)
{
sq_pushroottable(vm);
}
else
{
sq_pushobject(vm, slot.mThisRef);
}
// Are there any parameters to forward?
if (top > 1)
{
for (SQInteger i = 2; i <= top; ++i)
{
sq_push(vm, i);
}
}
// Make the function call and store the result
res = sq_call(vm, top, true, ErrorHandling::IsEnabled());
// Validate the result
if (SQ_FAILED(res))
{
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
// Stop emitting signals
break;
}
// Is the returned value not null?
else if (sq_gettype(vm, -1) != OT_NULL)
{
// Obtain the returned value
sq_tobool(vm, -1, &ret);
// Should we proceed to the next slot or stop here?
if (ret == SQFalse)
{
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
// The slot did not approve the signal
break;
}
}
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
}
// Did we fail to process slots?
if (SQ_FAILED(res))
{
return res; // Propagate the error
}
// Forward the returned value to the invoker
sq_pushbool(vm, ret);
// Specify that we returned something
return 1;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::Request(HSQUIRRELVM vm, SQInteger top)
{
// Are there any slots connected?
if (!m_Used) return 0;
// Enter a new execution scope
Scope scope(m_Scope, m_Slots, m_Slots + m_Used);
// Activate the current scope and create a guard to restore it
const AutoAssign< Scope * > aa(m_Scope, scope.mParent, &scope);
// Contains the last received result
SQRESULT res = SQ_OK;
// Process the slots from this scope
while (scope.mItr != scope.mEnd)
{
// Grab a reference to the current slot
const Slot & slot = *(scope.mItr++);
// Push the callback object
sq_pushobject(vm, slot.mFuncRef);
// Is there an explicit environment?
if (slot.mThisHash == 0)
{
sq_pushroottable(vm);
}
else
{
sq_pushobject(vm, slot.mThisRef);
}
// Are there any parameters to forward?
if (top > 1)
{
for (SQInteger i = 2; i <= top; ++i)
{
sq_push(vm, i);
}
}
// Make the function call and store the result
res = sq_call(vm, top, true, ErrorHandling::IsEnabled());
// Validate the result
if (SQ_FAILED(res))
{
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
// Stop emitting signals
break;
}
// Is the returned value not null?
else if (sq_gettype(vm, -1) != OT_NULL)
{
// Remove the callback object from the stack
sq_remove(vm, -2);
// Specify that we returned something
res = 1;
// The slot did not approve the signal
break;
}
// Pop the callback object and return value from the stack
sq_pop(vm, 2);
}
// Return the last result
return res;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::SqEmit(HSQUIRRELVM vm)
{
const SQInteger top = sq_gettop(vm);
// Contains the last received result
SQRESULT res = SQ_OK;
// Attempt to forward the call to the signal instance
try
{
// Attempt to grab the signal instance from the stack
Signal * signal = Var< Signal * >(vm, 1).value;
// Do we have a valid signal instance?
if (!signal)
{
res = sq_throwerror(vm, "Invalid signal instance");
}
// Forward the call to the signal instance
else
{
res = signal->Emit(vm, top);
}
}
catch (const Sqrat::Exception & e)
{
res = sq_throwerror(vm, e.what());
}
// The execution was successful
return res;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::SqQuery(HSQUIRRELVM vm)
{
const SQInteger top = sq_gettop(vm);
// Do we have the collector environment?
if (top <= 1)
{
return sq_throwerror(vm, "Missing collector environment");
}
// Do we have the collector callback?
else if (top <= 2)
{
return sq_throwerror(vm, "Missing collector callback");
}
// Contains the last received result
SQRESULT res = SQ_OK;
// Attempt to forward the call to the signal instance
try
{
// Attempt to grab the signal instance from the stack
Signal * signal = Var< Signal * >(vm, 1).value;
// Do we have a valid signal instance?
if (!signal)
{
res = sq_throwerror(vm, "Invalid signal instance");
}
// Forward the call to the signal instance
else
{
res = signal->Query(vm, top);
}
}
catch (const Sqrat::Exception & e)
{
res = sq_throwerror(vm, e.what());
}
// The execution was successful
return res;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::SqConsume(HSQUIRRELVM vm)
{
const SQInteger top = sq_gettop(vm);
// Contains the last received result
SQRESULT res = SQ_OK;
// Attempt to forward the call to the signal instance
try
{
// Attempt to grab the signal instance from the stack
Signal * signal = Var< Signal * >(vm, 1).value;
// Do we have a valid signal instance?
if (!signal)
{
res = sq_throwerror(vm, "Invalid signal instance");
}
// Forward the call to the signal instance
else
{
res = signal->Consume(vm, top);
}
}
catch (const Sqrat::Exception & e)
{
res = sq_throwerror(vm, e.what());
}
// The execution was successful
return res;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::SqApprove(HSQUIRRELVM vm)
{
const SQInteger top = sq_gettop(vm);
// Contains the last received result
SQRESULT res = SQ_OK;
// Attempt to forward the call to the signal instance
try
{
// Attempt to grab the signal instance from the stack
Signal * signal = Var< Signal * >(vm, 1).value;
// Do we have a valid signal instance?
if (!signal)
{
res = sq_throwerror(vm, "Invalid signal instance");
}
// Forward the call to the signal instance
else
{
res = signal->Approve(vm, top);
}
}
catch (const Sqrat::Exception & e)
{
res = sq_throwerror(vm, e.what());
}
// The execution was successful
return res;
}
// ------------------------------------------------------------------------------------------------
SQInteger Signal::SqRequest(HSQUIRRELVM vm)
{
const SQInteger top = sq_gettop(vm);
// Contains the last received result
SQRESULT res = SQ_OK;
// Attempt to forward the call to the signal instance
try
{
// Attempt to grab the signal instance from the stack
Signal * signal = Var< Signal * >(vm, 1).value;
// Do we have a valid signal instance?
if (!signal)
{
res = sq_throwerror(vm, "Invalid signal instance");
}
// Forward the call to the signal instance
else
{
res = signal->Request(vm, top);
}
}
catch (const Sqrat::Exception & e)
{
res = sq_throwerror(vm, e.what());
}
// The execution was successful
return res;
}
// ------------------------------------------------------------------------------------------------
void Signal::Terminate()
{
// Terminate named signals
for (const auto & s : s_Signals)
{
// Clear slots
s.second.first->ClearSlots();
// Release the name
s.second.first->m_Name.clear();
// Release whatever is in the user data
s.second.first->m_Data.Release();
}
// Finally clear the container itself
s_Signals.clear();
// Create a copy so we don't invalidate iterators when destructor removes the instances
FreeSignals fsig(s_FreeSignals);
// Terminate anonymous signals
for (const auto & s : fsig)
{
// Clear slots
s->ClearSlots();
// Release whatever is in the user data
s->m_Data.Release();
}
// Finally clear the container itself
s_FreeSignals.clear();
}
// ------------------------------------------------------------------------------------------------
LightObj Signal::CreateFree()
{
// Remember the current stack size
const StackGuard sg;
// Create the signal instance
DeleteGuard< Signal > dg(new Signal());
// Attempt to create the signal instance
ClassType< Signal >::PushInstance(DefaultVM::Get(), dg.Get());
// This is now managed by the script
dg.Release();
// Return the created signal
return Var< LightObj >(DefaultVM::Get(), -1).value;
}
// ------------------------------------------------------------------------------------------------
LightObj Signal::Create(const StackStrF & name)
{
// Validate the signal name
if (name.mLen <= 0)
{
return CreateFree();
}
// Create a copy of the name
String sname(name.mPtr, name.mLen);
// Compute the hash of the specified name
const std::size_t hash = std::hash< String >{}(sname);
// See if the signal already exists
for (const auto & e : s_Signals)
{
if (e.first == hash)
{
return e.second.second.mObj; // Found a match so let's return it
}
}
// Remember the current stack size
const StackGuard sg;
// Create the signal instance
DeleteGuard< Signal > dg(new Signal(std::move(sname)));
// Grab the signal instance pointer
Signal * ptr = dg.Get();
// Attempt to create the signal instance
ClassType< Signal >::PushInstance(DefaultVM::Get(), ptr);
// This is now managed by the script
dg.Release();
// Grab a reference to the instance created on the stack
s_Signals.emplace_back(hash, SignalPair(ptr, Var< LightObj >(DefaultVM::Get(), -1).value));
// Return the created signal
return s_Signals.back().second.second.mObj;
}
// ------------------------------------------------------------------------------------------------
void Signal::Remove(const StackStrF & name)
{
// Validate the signal name
if (name.mLen <= 0)
{
//STHROWF("Signals without names cannot be removed manually");
}
// Create a copy of the name
const String sname(name.mPtr, name.mLen);
// Compute the hash of the specified name
const std::size_t hash = std::hash< String >{}(sname);
// Iterator to the existing signal, if any
SignalPool::const_iterator itr = s_Signals.cbegin();
// Search for a signal with this name
for (; itr != s_Signals.cend(); ++itr)
{
if (itr->first == hash)
{
break;
}
}
// Did we find anything?
if (itr != s_Signals.cend())
{
// Clear the name
itr->second.first->m_Name.clear();
// Put it on the free list
s_FreeSignals.push_back(itr->second.first);
// Finally, remove it from the named list
s_Signals.erase(itr);
}
}
// ------------------------------------------------------------------------------------------------
const LightObj & Signal::Fetch(const StackStrF & name)
{
// Validate the signal name
if (name.mLen <= 0)
{
//STHROWF("Signals without names cannot be retrieved manually");
}
// Create a copy of the name
const String sname(name.mPtr, name.mLen);
// Compute the hash of the specified name
const std::size_t hash = std::hash< String >{}(sname);
// Search for a signal with this name
for (const auto & e : s_Signals)
{
if (e.first == hash)
{
return e.second.second; // Found a match so let's return it
}
}
// No such signal exists
//STHROWF("Unknown signal named (%s)", sname.c_str());
// SHOULD NOT REACH THIS POINT!
static LightObj slo;
return slo;
}
/* ------------------------------------------------------------------------------------------------
* Forward the call to terminate the signals.
*/
void TerminateSignals()
{
Signal::Terminate();
}
// ------------------------------------------------------------------------------------------------
void InitSignalPair(SignalPair & sp, LightObj & et, const char * name)
{
// Remember the current stack size
const StackGuard sg;
// Create the signal instance
DeleteGuard< Signal > dg(new Signal());
// Attempt to create the signal instance object
sp.second = LightObj(dg.Get());
// Assign the signal instance itself
sp.first = dg.Get();
// This is now managed by the script
dg.Release();
// Should we bind this to a certain object?
if (name != nullptr)
{
et.Bind(name, sp.second); // Bind the signal to the specified object
}
}
// ------------------------------------------------------------------------------------------------
void ResetSignalPair(SignalPair & sp, bool clear)
{
// See if the slots must be cleared as well
if (clear && sp.first != nullptr)
{
sp.first->ClearSlots();
}
// Reset the signal pair
sp.first = nullptr;
sp.second.Release();
}
// ================================================================================================
void Register_Signal(HSQUIRRELVM vm)
{
RootTable(vm).Bind(Typename::Str,
Class< Signal, NoConstructor< Signal > >(vm, Typename::Str)
// Meta-methods
.SquirrelFunc(_SC("_typename"), &Typename::Fn)
.Func(_SC("_tostring"), &Signal::ToString)
// Core Properties
.Prop(_SC("Data"), &Signal::GetData, &Signal::SetData)
.Func(_SC("Name"), &Signal::ToString)
.Prop(_SC("Slots"), &Signal::GetUsed)
.Prop(_SC("Empty"), &Signal::IsEmpty)
// Core Methods
.Func(_SC("Clear"), &Signal::ClearSlots)
// Squirrel Functions
.SquirrelFunc(_SC("Connect"), &Signal::SqConnect)
.SquirrelFunc(_SC("ConnectOnce"), &Signal::SqConnectOnce)
.SquirrelFunc(_SC("Exists"), &Signal::SqExists)
.SquirrelFunc(_SC("Disconnect"), &Signal::SqDisconnect)
.SquirrelFunc(_SC("ExistsThis"), &Signal::SqExistsThis)
.SquirrelFunc(_SC("ExistsFunc"), &Signal::SqExistsFunc)
.SquirrelFunc(_SC("Count"), &Signal::SqCount)
.SquirrelFunc(_SC("CountThis"), &Signal::SqCountThis)
.SquirrelFunc(_SC("CountFunc"), &Signal::SqCountFunc)
.SquirrelFunc(_SC("Lead"), &Signal::SqLead)
.SquirrelFunc(_SC("LeadThis"), &Signal::SqLeadThis)
.SquirrelFunc(_SC("LeadFunc"), &Signal::SqLeadFunc)
.SquirrelFunc(_SC("Tail"), &Signal::SqTail)
.SquirrelFunc(_SC("TailThis"), &Signal::SqTailThis)
.SquirrelFunc(_SC("TailFunc"), &Signal::SqTailFunc)
.SquirrelFunc(_SC("Eliminate"), &Signal::SqEliminate)
.SquirrelFunc(_SC("EliminateThis"), &Signal::SqEliminateThis)
.SquirrelFunc(_SC("EliminateFunc"), &Signal::SqEliminateFunc)
.SquirrelFunc(_SC("Emit"), &Signal::SqEmit)
.SquirrelFunc(_SC("Query"), &Signal::SqQuery)
.SquirrelFunc(_SC("Consume"), &Signal::SqConsume)
.SquirrelFunc(_SC("Approve"), &Signal::SqApprove)
.SquirrelFunc(_SC("Request"), &Signal::SqRequest)
);
RootTable(vm)
.FmtFunc(_SC("SqSignal"), &Signal::Fetch)
.FmtFunc(_SC("SqCreateSignal"), &Signal::Create)
.FmtFunc(_SC("SqRemoveSignal"), &Signal::Remove);
}
} // Namespace:: SqMod