mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
439 lines
12 KiB
C++
439 lines
12 KiB
C++
#include "stdafx.h"
|
|
|
|
#include "Script/LuaSignalBridge.h"
|
|
|
|
#include "Script/Script.h"
|
|
#include "Script/ScriptContext.h"
|
|
#include "Script/LuaInstanceBridge.h"
|
|
#include "Script/LuaAtomicClasses.h"
|
|
#include "script/LuaEnum.h"
|
|
#include "Script/ScriptEvent.h"
|
|
#include "Script/LuaArguments.h"
|
|
#include "reflection/EnumConverter.h"
|
|
#include "util/ScopedAssign.h"
|
|
#include "util/NormalId.h"
|
|
#include "util/ProtectedString.h"
|
|
#include "util/standardout.h"
|
|
|
|
LOGVARIABLE(LuaBridge, 0)
|
|
DYNAMIC_FASTFLAGVARIABLE(UseSubmitTaskWhenFiringSignalsOnSettings, true)
|
|
|
|
namespace RBX { namespace Lua {
|
|
|
|
template<>
|
|
const char* Bridge<EventInstance>::className("RBXScriptSignal");
|
|
|
|
template<>
|
|
int Bridge<EventInstance>::on_index(const EventInstance& object, const char* name, lua_State *L)
|
|
{
|
|
// The pre-defined "connect()" method
|
|
if (strcmp(name, "connect")==0)
|
|
{
|
|
lua_pushcfunction(L, EventBridge::connect);
|
|
return 1;
|
|
}
|
|
|
|
// TODO: Remove this when we remove it from any Roblox scripts.
|
|
if (strcmp(name, "connectFirst")==0)
|
|
{
|
|
RBX::Security::Context::current().requirePermission(RBX::Security::LocalUser, "connectFirst");
|
|
StandardOut::singleton()->print(MESSAGE_WARNING, "connectFirst is deprecated");
|
|
lua_pushcfunction(L, EventBridge::connect);
|
|
return 1;
|
|
}
|
|
|
|
// TODO: Remove this when we remove it from any Roblox scripts.
|
|
if (strcmp(name, "connectLast")==0)
|
|
{
|
|
RBX::Security::Context::current().requirePermission(RBX::Security::LocalUser, "connectLast");
|
|
StandardOut::singleton()->print(MESSAGE_WARNING, "connectLast is deprecated");
|
|
lua_pushcfunction(L, EventBridge::connect);
|
|
return 1;
|
|
}
|
|
|
|
// The pre-defined "wait()" method
|
|
if (strcmp(name, "wait")==0)
|
|
{
|
|
lua_pushcfunction(L, EventBridge::wait);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(name, "disconnect")==0)
|
|
throw RBX::runtime_error("Event:disconnect() has been deprecated. Use connection object returned by connect()");
|
|
|
|
// Failure
|
|
throw RBX::runtime_error("%s is not a valid member", name);
|
|
}
|
|
|
|
template<>
|
|
void Bridge<EventInstance>::on_newindex(EventInstance& object, const char* name, lua_State *L)
|
|
{
|
|
throw RBX::runtime_error("%s cannot be assigned to", name);
|
|
}
|
|
|
|
// This Slot is used when a script calls "connect" on a Signal
|
|
class FunctionScriptSlot
|
|
: public BaseScript::Slot
|
|
{
|
|
typedef Reflection::TGenericSlotWrapper<FunctionScriptSlot> WrapperType;
|
|
const Reflection::EventDescriptor* descriptor;
|
|
ScriptContext& context;
|
|
WeakFunctionRef function;
|
|
WeakThreadRef cachedSlotThread; // cached thread to be re-used if available
|
|
int executionDepth;
|
|
bool useSubmitTask;
|
|
weak_ptr<WrapperType> weakSelf;
|
|
weak_ptr<DataModel> weakDm;
|
|
public:
|
|
|
|
FunctionScriptSlot(const Reflection::EventDescriptor* descriptor, lua_State* thread, int functionIndex, bool useSubmitTask)
|
|
: descriptor(descriptor)
|
|
, context(ScriptContext::getContext(thread))
|
|
, function(thread, functionIndex)
|
|
, executionDepth(0)
|
|
, useSubmitTask(useSubmitTask)
|
|
{
|
|
if (useSubmitTask)
|
|
{
|
|
weakDm = weak_from(DataModel::get(&context));
|
|
}
|
|
}
|
|
|
|
void setWeakSelf(weak_ptr<WrapperType> weakSelf)
|
|
{
|
|
this->weakSelf = weakSelf;
|
|
}
|
|
|
|
void operator()(const Reflection::EventArguments& arguments)
|
|
{
|
|
if (useSubmitTask)
|
|
{
|
|
if (shared_ptr<DataModel> dm = weakDm.lock())
|
|
{
|
|
dm->submitTask(boost::bind(&FunctionScriptSlot::safeDoEventFire,
|
|
weakSelf, arguments /*copy arguments*/),
|
|
DataModelJob::Write);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
doEventFire(arguments);
|
|
}
|
|
}
|
|
|
|
static void safeDoEventFire(weak_ptr<WrapperType> weakSelf, const Reflection::EventArguments& arguments)
|
|
{
|
|
if (shared_ptr<WrapperType> self = weakSelf.lock())
|
|
{
|
|
self->slot.doEventFire(arguments);
|
|
}
|
|
}
|
|
|
|
void doEventFire(const Reflection::EventArguments& arguments)
|
|
{
|
|
bool reentrant = executionDepth>0;
|
|
RBX::ScopedAssign<int> assign(executionDepth, executionDepth+1);
|
|
|
|
if (executionDepth>6)
|
|
{
|
|
// This should help against stack crashes... Note that you could have a round-robin
|
|
// re-entrancy problem. In that case the stack may contain 1 or 18 or more calls!
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "Maximum event re-entrancy depth exceeded for %s.%s", descriptor->owner.name.c_str(), descriptor->name.c_str());
|
|
|
|
if (ThreadRef functionThread = function.lock())
|
|
{
|
|
lua_pushfunction(functionThread, function);
|
|
|
|
lua_Debug ar;
|
|
if (lua_getinfo(functionThread, ">S", &ar))
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "While entering function defined in script '%s', line %d", ar.short_src, ar.linedefined);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (ThreadRef functionThread = function.lock())
|
|
{
|
|
//Count the number of invocations here per 'cycle'
|
|
|
|
FASTLOG1(FLog::LuaBridge, "Executing lua from signal, execution depth: %u", executionDepth);
|
|
|
|
// If this is a re-entrant call, then we can't use the cached thread
|
|
ThreadRef slotThread;
|
|
if(!reentrant)
|
|
slotThread = cachedSlotThread.lock();
|
|
|
|
{
|
|
RBXASSERT_BALLANCED_LUA_STACK(functionThread); //oldTop
|
|
|
|
bool createdThread;
|
|
if (slotThread)
|
|
createdThread = false;
|
|
else
|
|
{
|
|
// Create a child thread that will handle the event
|
|
|
|
slotThread = lua_newthread(functionThread);
|
|
|
|
RBXASSERT(lua_isthread(functionThread, -1));
|
|
|
|
if (!slotThread)
|
|
throw std::runtime_error("Unable to create a new event handler thread");
|
|
createdThread = true;
|
|
|
|
// Pop slotThread from the parent's stack
|
|
RBXASSERT(lua_isthread(functionThread, -1)); // slotThread
|
|
lua_pop(functionThread, 1); // [empty]
|
|
}
|
|
//createdThread ? (slotThread) : [empty]
|
|
// Push the function onto the stack
|
|
lua_pushfunction(slotThread, function);
|
|
|
|
// Push arguments onto slotThread's stack
|
|
const int nargs = LuaArguments::pushValues(arguments, slotThread);
|
|
|
|
// resume slotThread
|
|
const ScriptContext::Result result = context.resume(slotThread, nargs);
|
|
|
|
switch (result)
|
|
{
|
|
case ScriptContext::Yield:
|
|
// we can't re-use a yielding thread, since somebody else will resume it later
|
|
if (!reentrant)
|
|
cachedSlotThread.reset();
|
|
break;
|
|
|
|
case ScriptContext::Error:
|
|
// Don't re-use this thread
|
|
if (!reentrant)
|
|
cachedSlotThread.reset();
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (function.empty())
|
|
{
|
|
// The thread that hosts our function has (or is being) terminated
|
|
RBXASSERT(cachedSlotThread.empty());
|
|
disconnect();
|
|
return;
|
|
}
|
|
|
|
if (createdThread)
|
|
{
|
|
if (!reentrant)
|
|
if (result==ScriptContext::Success)
|
|
// Since the slot didn't yield, we can re-use this thread the next time
|
|
cachedSlotThread = WeakThreadRef(slotThread);
|
|
}
|
|
}
|
|
RBXASSERT(slotThread);
|
|
// balance the slot stack
|
|
lua_resetstack(slotThread, 0);
|
|
}
|
|
else
|
|
{
|
|
// The thread that hosts our function has (or is being) terminated
|
|
RBXASSERT(cachedSlotThread.empty());
|
|
disconnect();
|
|
return;
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
// This Slot is used when a script calls "wait" on a Signal
|
|
class WaitScriptSlot
|
|
: public BaseScript::Slot
|
|
{
|
|
typedef Reflection::TGenericSlotWrapper<WaitScriptSlot> WrapperType;
|
|
WeakThreadRef waitThread;
|
|
bool useSubmitTask;
|
|
weak_ptr<DataModel> weakDm;
|
|
weak_ptr<WrapperType> weakSelf;
|
|
public:
|
|
WaitScriptSlot(lua_State* thread, bool useSubmitTask)
|
|
:waitThread(thread)
|
|
,useSubmitTask(useSubmitTask)
|
|
{
|
|
if (useSubmitTask)
|
|
{
|
|
weakDm = weak_from(DataModel::get(&ScriptContext::getContext(thread)));
|
|
}
|
|
}
|
|
|
|
void setWeakSelf(weak_ptr<WrapperType> weakSelf)
|
|
{
|
|
this->weakSelf = weakSelf;
|
|
}
|
|
|
|
void operator()(const Reflection::EventArguments& arguments)
|
|
{
|
|
// Waits are one-shots, so disconnect now
|
|
disconnect();
|
|
if (useSubmitTask)
|
|
{
|
|
if (shared_ptr<DataModel> dm = weakDm.lock())
|
|
{
|
|
dm->submitTask(boost::bind(&WaitScriptSlot::safeDoEventFire,
|
|
weakSelf, arguments /*copy arguments*/),
|
|
DataModelJob::Write);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
doEventFire(arguments);
|
|
}
|
|
}
|
|
|
|
static void safeDoEventFire(weak_ptr<WrapperType> weakSelf, const Reflection::EventArguments& arguments)
|
|
{
|
|
if (shared_ptr<WrapperType> self = weakSelf.lock())
|
|
{
|
|
self->slot.doEventFire(arguments);
|
|
}
|
|
}
|
|
|
|
void doEventFire(const Reflection::EventArguments& arguments)
|
|
{
|
|
if (ThreadRef thread = waitThread.lock())
|
|
{
|
|
// Push arguments onto slotThread's stack
|
|
const int nargs = LuaArguments::pushValues(arguments, thread);
|
|
|
|
// resume the waiting thread
|
|
ScriptContext::getContext(thread).resume(thread, nargs);
|
|
|
|
// After a resume the thread has either yielded, returned
|
|
// or thrown an error. In all 3 cases we want to drop
|
|
// any return arguments
|
|
lua_resetstack(thread, 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
int EventBridge::connect(lua_State *L)
|
|
{
|
|
RobloxExtraSpace* space = RobloxExtraSpace::get(L);
|
|
EventInstance& ei(getObject(L, 1));
|
|
|
|
rbx::signals::connection connection;
|
|
|
|
shared_ptr<Reflection::DescribedBase> source = ei.source.lock();
|
|
if (source && ei.descriptor->isScriptable() && !space->context()->shouldPreventNewConnections())
|
|
{
|
|
if (ei.descriptor->security!=RBX::Security::None)
|
|
RBX::Security::Context::current().requirePermission(ei.descriptor->security, ei.descriptor->name.c_str());
|
|
|
|
Reflection::Variant varResult;
|
|
Lua::LuaArguments::get(L, 2, varResult, false);
|
|
if (!varResult.isType<Lua::WeakFunctionRef>())
|
|
{
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Attempt to connect failed: Passed value is not a function");
|
|
ScriptContext::printCallStack(L);
|
|
}
|
|
|
|
bool useSubmitTask = DFFlag::UseSubmitTaskWhenFiringSignalsOnSettings && source->useSubmitTaskForLuaListeners();
|
|
|
|
shared_ptr< RBX::Reflection::TGenericSlotWrapper<FunctionScriptSlot> > wrapper(
|
|
rbx::make_shared< RBX::Reflection::TGenericSlotWrapper<FunctionScriptSlot> >(FunctionScriptSlot(ei.descriptor, L, 2, useSubmitTask))
|
|
);
|
|
if (useSubmitTask)
|
|
{
|
|
wrapper->slot.setWeakSelf(wrapper);
|
|
}
|
|
connection = ei.descriptor->connectGeneric(source.get(), wrapper);
|
|
wrapper->slot.assignConnection(connection);
|
|
}
|
|
|
|
// Return a proxy to the connection object
|
|
SignalConnectionBridge::pushNewObject(L, connection);
|
|
return 1;
|
|
}
|
|
|
|
int EventBridge::wait(lua_State *L)
|
|
{
|
|
{
|
|
RBXASSERT_BALLANCED_LUA_STACK(L);
|
|
|
|
EventInstance& ei(getObject(L, 1));
|
|
|
|
shared_ptr<Reflection::DescribedBase> source = ei.source.lock();
|
|
if (source && ei.descriptor->isScriptable())
|
|
{
|
|
if (ei.descriptor->security!=RBX::Security::None)
|
|
RBX::Security::Context::current().requirePermission(ei.descriptor->security, ei.descriptor->name.c_str());
|
|
|
|
bool useSubmitTask = DFFlag::UseSubmitTaskWhenFiringSignalsOnSettings && source->useSubmitTaskForLuaListeners();
|
|
|
|
shared_ptr<RBX::Reflection::TGenericSlotWrapper<WaitScriptSlot> > wrapper(
|
|
rbx::make_shared< RBX::Reflection::TGenericSlotWrapper<WaitScriptSlot> >(WaitScriptSlot(L, useSubmitTask))
|
|
);
|
|
if (useSubmitTask)
|
|
{
|
|
wrapper->slot.setWeakSelf(wrapper);
|
|
}
|
|
wrapper->slot.assignConnection(ei.descriptor->connectGeneric(source.get(), wrapper));
|
|
}
|
|
|
|
RBXASSERT(!RobloxExtraSpace::get(L)->yieldCaptured);
|
|
RobloxExtraSpace::get(L)->yieldCaptured = true;
|
|
}
|
|
|
|
return lua_yield(L, 0);
|
|
}
|
|
|
|
template<>
|
|
const char* Bridge< rbx::signals::connection >::className("RBXScriptConnection");
|
|
|
|
template<>
|
|
int Bridge< rbx::signals::connection >::on_index(const rbx::signals::connection& object, const char* name, lua_State *L)
|
|
{
|
|
// The pre-defined "disconnect()" method
|
|
if (strcmp(name, "disconnect")==0)
|
|
{
|
|
lua_pushcfunction(L, SignalConnectionBridge::disconnect);
|
|
return 1;
|
|
}
|
|
|
|
// The pre-defined "connected" property
|
|
if (strcmp(name, "connected")==0)
|
|
{
|
|
lua_pushboolean(L, object.connected());
|
|
return 1;
|
|
}
|
|
|
|
// Failure
|
|
throw RBX::runtime_error("%s is not a valid member", name);
|
|
}
|
|
|
|
template<>
|
|
void Bridge< rbx::signals::connection >::on_newindex(rbx::signals::connection& object, const char* name, lua_State *L)
|
|
{
|
|
throw RBX::runtime_error("%s cannot be assigned to", name);
|
|
}
|
|
|
|
int SignalConnectionBridge::disconnect(lua_State *L) {
|
|
|
|
getObject(L, 1).disconnect();
|
|
|
|
return 0;
|
|
}
|
|
|
|
// The default implementation for on_tostring is available in LuaBridge.cpp. This is a specialization.
|
|
template<>
|
|
int Bridge<EventInstance>::on_tostring(const EventInstance& object, lua_State *L)
|
|
{
|
|
std::string s = "Signal ";
|
|
s += object.descriptor->name.toString();
|
|
lua_pushstring(L, s);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|