Skip to content

Commit

Permalink
Engine: move call_function() and GlobalReturnValue to ccInstance
Browse files Browse the repository at this point in the history
+ updated comments
  • Loading branch information
ivan-mogilko committed Dec 20, 2024
1 parent 2b848b1 commit 8795c02
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 168 deletions.
9 changes: 4 additions & 5 deletions Engine/plugin/agsplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ extern RGB palette[256];
extern int displayed_room;
extern RoomStruct thisroom;
extern RoomStatus *croom;
extern RuntimeScriptValue GlobalReturnValue;


// **************** PLUGIN IMPLEMENTATION ****************
Expand Down Expand Up @@ -682,7 +681,7 @@ int IAGSEngine::RegisterManagedObject(void *object, IAGSScriptManagedObject *cal
// we may try to optimize following by having a cache of CCPluginObjects per callback
// address. Need to research if that's reliable, and will actually be more performant.
auto *pl_obj = new CCPluginObject((IScriptObject*)callback);
GlobalReturnValue.SetPluginObject((void*)object, pl_obj);
ccInstance::SetPluginReturnValue(RuntimeScriptValue().SetPluginObject((void*)object, pl_obj));
return ccRegisterManagedObject(object, pl_obj, kScValPluginObject);
}

Expand All @@ -700,7 +699,7 @@ void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectR

void IAGSEngine::RegisterUnserializedObject(int key, void *object, IAGSScriptManagedObject *callback) {
auto *pl_obj = new CCPluginObject((IScriptObject*)callback);
GlobalReturnValue.SetPluginObject((void*)object, pl_obj);
ccInstance::SetPluginReturnValue(RuntimeScriptValue().SetPluginObject((void*)object, pl_obj));
ccRegisterUnserializedObject(key, object, pl_obj, kScValPluginObject);
}

Expand All @@ -712,14 +711,14 @@ void* IAGSEngine::GetManagedObjectAddressByKey(int key) {
void *object;
IScriptObject *manager;
ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(key, object, manager);
GlobalReturnValue.SetScriptObject(obj_type, object, manager);
ccInstance::SetPluginReturnValue(RuntimeScriptValue().SetScriptObject(obj_type, object, manager));
return object;
}

const char* IAGSEngine::CreateScriptString(const char *fromText) {
const char *string = CreateNewScriptString(fromText);
// Should be still standard dynamic object, because not managed by plugin
GlobalReturnValue.SetScriptObject((void*)string, &myScriptStringImpl);
ccInstance::SetPluginReturnValue(RuntimeScriptValue().SetScriptObject((void*)string, &myScriptStringImpl));
return string;
}

Expand Down
179 changes: 160 additions & 19 deletions Engine/script/cc_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,6 @@ ccInstance *LoadedInstances[MAX_PRIMARY_INSTANCES] = { nullptr };
// An example situation is repeatedly_execute_always callback running while
// another instance is waiting at the blocking action or Wait().
std::deque<ccInstance*> InstThreads;
// [IKM] 2012-10-21:
// NOTE: This is temporary solution (*sigh*, one of many) which allows certain
// exported functions return value as a RuntimeScriptValue object;
// Of 2012-12-20: now used only for plugin exports
// FIXME: re-investigate this, find if it's possible to get rid of this.
RuntimeScriptValue GlobalReturnValue;


String cc_get_callstack(int max_lines)
Expand Down Expand Up @@ -209,6 +203,7 @@ struct FunctionCallStack
};


RuntimeScriptValue ccInstance::_pluginReturnValue;
unsigned ccInstance::_timeoutCheckMs = 60u;
unsigned ccInstance::_timeoutAbortMs = 0u;
unsigned ccInstance::_maxWhileLoops = 0u;
Expand Down Expand Up @@ -253,6 +248,11 @@ void ccInstance::SetExecTimeout(const unsigned sys_poll_ms, const unsigned abort
_maxWhileLoops = abort_loops;
}

void ccInstance::SetPluginReturnValue(const RuntimeScriptValue &value)
{
_pluginReturnValue = value;
}

ccInstance::~ccInstance()
{
Free();
Expand Down Expand Up @@ -1307,26 +1307,15 @@ ccInstError ccInstance::Run(int32_t curpc)

if (reg1.Type == kScValPluginFunction)
{
GlobalReturnValue.Invalidate();
int32_t int_ret_val;
if (next_call_needs_object)
{
RuntimeScriptValue obj_rval = _registers[SREG_OP];
obj_rval.DirectPtrObj();
int_ret_val = call_function(reg1.Ptr, &obj_rval, num_args_to_func, func_callstack.GetHead() + 1);
}
else
{
int_ret_val = call_function(reg1.Ptr, nullptr, num_args_to_func, func_callstack.GetHead() + 1);
}

if (GlobalReturnValue.IsValid())
{
return_value = GlobalReturnValue;
return_value = CallPluginFunction(reg1.Ptr, &obj_rval, func_callstack.GetHead() + 1, num_args_to_func);
}
else
{
return_value.SetPluginArgument(int_ret_val);
return_value = CallPluginFunction(reg1.Ptr, nullptr, func_callstack.GetHead() + 1, num_args_to_func);
}
}
else if (next_call_needs_object)
Expand Down Expand Up @@ -2214,6 +2203,158 @@ void ccInstance::CopyGlobalData(const std::vector<uint8_t> &data)
std::copy(data.begin(), data.begin() + copy_sz, _scriptData->globaldata.begin());
}

RuntimeScriptValue ccInstance::CallPluginFunction(void *fn_addr, const RuntimeScriptValue *object, const RuntimeScriptValue *params, int param_count)
{
assert(fn_addr);
assert(param_count == 0 || params);
if (!fn_addr)
{
cc_error("Null function pointer in CallPluginFunction");
return {};
}
if (param_count > 0 && !params)
{
cc_error("Invalid parameters array in CallPluginFunction");
return {};
}

intptr_t parm_value[9];
if (object)
{
parm_value[0] = (intptr_t)object->GetPtrWithOffset();
param_count++;
}

for (int ival = object ? 1 : 0, iparm = 0; ival < param_count; ++ival, ++iparm)
{
switch (params[iparm].Type)
{
case kScValInteger:
case kScValFloat: // AGS passes floats, copying their values into long variable
case kScValPluginArg:
parm_value[ival] = (intptr_t)params[iparm].IValue;
break;
default:
parm_value[ival] = (intptr_t)params[iparm].GetPtrWithOffset();
break;
}
}

//
// Here we are sending parameters of type intptr_t to a registered function
// of unknown kind. Intptr_t is 32-bit or 64-bit depending on a build.
// The exported functions usually have two types of parameters: pointer and
// 'int' (32-bit). For x32 build those two have the same size, but for x64
// build pointer has 64-bit size while the 'int' remains 32-bit.
// In a formal case that would cause 'overflow' - function will receive more
// data than needed (written to stack), with some values shifted further by
// 32 bits.
//
// Upon testing, however, it was revealed that 64-bit processors usually
// treat all the function parameters pushed to stack as 64-bit values
// (few first parameters may be sent via registers, and hence are of least
// concern anyway). Therefore, no 'overflow' occurs, and 64-bit values are
// effectively truncated to 32-bit integers in the callee.
//
// Formally speaking, this is still unreliable, but we have to live with
// this for the time being, until a more suitable solution is found and
// implemented.
//
// One basic idea would be to pass array of RuntimeScriptValues and get
// same RSV as a return result, just like we do with the engine's own exports.
// Needless to say that such approach would require a breaking change in plugin API.
//

_pluginReturnValue.Invalidate();

intptr_t result;
switch (param_count)
{
case 0:
{
intptr_t (*fparam) ();
fparam = (intptr_t (*)())fn_addr;
result = fparam();
break;
}
case 1:
{
intptr_t (*fparam) (intptr_t);
fparam = (intptr_t (*)(intptr_t))fn_addr;
result = fparam(parm_value[0]);
break;
}
case 2:
{
intptr_t (*fparam) (intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1]);
break;
}
case 3:
{
intptr_t (*fparam) (intptr_t, intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1], parm_value[2]);
break;
}
case 4:
{
intptr_t (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t, intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3]);
break;
}
case 5:
{
intptr_t (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4]);
break;
}
case 6:
{
intptr_t (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5]);
break;
}
case 7:
{
intptr_t (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5], parm_value[6]);
break;
}
case 8:
{
intptr_t (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5], parm_value[6], parm_value[7]);
break;
}
case 9:
{
intptr_t (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
fparam = (intptr_t (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))fn_addr;
result = fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5], parm_value[6], parm_value[7], parm_value[8]);
break;
}
default:
cc_error("Too many arguments in call to plugin function");
return {};
}

if (_pluginReturnValue.IsValid())
{
return _pluginReturnValue;
}
else
{
return RuntimeScriptValue().SetPluginArgument(static_cast<int32_t>(result));
}
}

void ccInstance::PushValueToStack(const RuntimeScriptValue &rval)
{
// Write value to the stack tail and advance stack ptr
Expand Down
17 changes: 17 additions & 0 deletions Engine/script/cc_instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class ccInstance
static std::unique_ptr<ccInstance> CreateFromScript(PScript script);
static std::unique_ptr<ccInstance> CreateEx(PScript scri, const ccInstance * joined);
static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
static void SetPluginReturnValue(const RuntimeScriptValue &value);

ccInstance() = default;
~ccInstance();
Expand Down Expand Up @@ -211,6 +212,9 @@ class ccInstance
// Begin executing script starting from the given bytecode index
ccInstError Run(int32_t curpc);

// For calling exported plugin functions old-style
RuntimeScriptValue CallPluginFunction(void *fn_addr, const RuntimeScriptValue *object, const RuntimeScriptValue *params, int param_count);

// Stack processing
// Push writes new value and increments stack ptr;
// stack ptr now points to the __next empty__ entry
Expand Down Expand Up @@ -286,6 +290,19 @@ class ccInstance
int _lineNumber = 0; // source code line number
int _returnValue = 0; // last executed function's return value

// A value returned from plugin functions saved as RuntimeScriptValue.
// This is a temporary solution (*sigh*, one of many) which allows to
// receive any pointer values from plugins, as RSV can only store plain
// numeric values as 32-bit integers. Not to mention that this way we
// can store an accompanying IScriptObject manager for managed objects.
// The big problem with this is that plugins do not know about RSV,
// so engine has to make educated GUESS, and assign this whenever plugin
// prepares a pointer, e.g. by registering or retrieving a managed object.
// This works, let's say, 95% of cases in practice, but is not formally
// reliable.
// FIXME: re-investigate this, find if there's a better solution?
static RuntimeScriptValue _pluginReturnValue;

// Minimal timeout: how much time may pass without any engine update
// before we want to check on the situation and do system poll
static unsigned _timeoutCheckMs;
Expand Down
Loading

0 comments on commit 8795c02

Please sign in to comment.