GameMonkey Script

GameMonkey Script Forums
It is currently Mon Nov 20, 2017 3:26 pm

All times are UTC




Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: Sun May 13, 2012 4:26 pm 
Offline

Joined: Sun May 13, 2012 4:16 pm
Posts: 5
Is there a way to create a series of functions from a single script string, and place them outside of an instantiated gmMachine object?

I have the following restrictions on my use of GM:

* I have thousands of game objects that need a unique script of their own (I tried doing gmMachine machine[1024], and that used over 70 megabytes of RAM) [I refuse]
* Objects use non-unique function names for event functions, such as onDeath() etc

I've heard about CompileStringToLib() and ExecuteLib(), but I still need a way to differentiate between two identically named functions from two separate game objects.

My apologies if I haven't explained this properly. Thanks.


Top
 Profile  
Reply with quote  
PostPosted: Mon May 14, 2012 12:31 pm 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 698
Welcome to the forum Truncheon :)

I'm not sure I completely understand what you're trying to achieve here. I think you're saying... You have lots of unique scripts that perhaps represent unique actions or events for individual game entities. You would like to pre-compile the scripts so you can store or manage them outside the gmMachine. Non-unique names is a concern for you because they would otherwise compete for the global table name space.

My thoughts so far: You certainly don't want multiple gmMachines. I don't think you really need to pre-compile and store gmFunctionObjects yourself, though that could work as a way to store unnamed functions.

I guess one way of retrieving functions could be to return them from another eg.
Code:
global GetFunc = function(a_funcNum)
{
  if( a_funcNum == 0 )
  { return function(){print("func0");};  }
  else
  { return function(){print("func1");};  }
};

funcA = GetFunc(0);
funcB = GetFunc(1);
funcA();
funcB();

// Output:
// func0
// func1

The most common thing to do in games is not use the global table much, instead assign member functions on an entities 'this' table.eg.
Code:
// Pass entity as 'this' to Execute()
Init = function()
{
  .OnDeath() = function(a_killer)
  {  // ...Function body...
  };

  .OnHit() = function(a_instigator)
  {  // ...Function body...
  };
};

// or pass entity as parameter to temporary global function

global Init = function(a_objToInit)
{
  a_objToInit.OnDeath() = function(a_killer)
  {  // ...Function body...
  };

  a_objToInit.OnHit() = function(a_instigator)
  {  // ...Function body...
  };
};

You can store these scripts in individual files/string. The file/string represents an unnamed function itself. You can either use a temporary global function and pass the entity to fill its event functions as parameter, or probably better, just pass the entity as 'this' to the executed file/string. This way you'd specialize behavior for individual instances. You can use this to override 'type functions' and you can swap functions at run-time for logical state changes if needed.

Does this make some sense or sound like what you need?


Top
 Profile  
Reply with quote  
PostPosted: Tue May 15, 2012 8:25 am 
Offline

Joined: Sun May 13, 2012 4:16 pm
Posts: 5
Are you absolutely sure that those three examples you just showed me are the only way of achieving what I need? If so, there's just no way I can use game monkey to do what I need. It's a shame, because rolling my own scripting language could take months of work, and it will not likely be as fast/optimized.

I'm not just making a game, it's a moddable GCS as well, so I can't really expect users to type out the daft code you just showed me. It has to be as simple as something like the following:

Individual game entity's script:

Code:
non_event_function() // callable only from a function within this entity (unique and non global)
{

}

event_function_1() // entry point to the script via event (unique and non global)
{
    //code
    // another entity may have the same event function, but it's code may be different
}


Also the scripts have to be associated with the owning game entity, and there can't be any mention of global, or "temporary global" functions as you just mentioned. That would be far too confusing for potential users of the game system.

Lastly, there is nothing "global". The users can use one of my API functions to create a global variable in the following manner (otherwise there is no need to have anything global):

Code:
function()
{
    //code
    external_C_Function_SetVariable("VarName", ref_to_var);
}


Thanks for your help.


Top
 Profile  
Reply with quote  
PostPosted: Tue May 15, 2012 12:48 pm 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 698
Sorry, there was no need for the Init() stuff, just try this slight modification to your code:
Code:
.non_event_function = function() // callable only from a function within this entity (unique and non global)
{
}

.event_function_1 = function() // entry point to the script via event (unique and non global)
{
    //code
    // another entity may have the same event function, but it's code may be different
}

Now put that code in string/file and execute with entity as 'this'.

For slightly nicer syntax you could change #define GMMACHINE_DEFAULT_FUNCTION from CTVT_LOCAL to CTVT_MEMBER in gmConfig.h
Then the code could look like:
Code:
function non_event_function() // callable only from a function within this entity (unique and non global)
{
}

function event_function_1() // entry point to the script via event (unique and non global)
{
    //code
    // another entity may have the same event function, but it's code may be different
}

All that's going on there is the functions are assigned as member variables as if you wrote:
Code:
someEntity.event_function_1 = function()
{
}

You can execute from text or precompiled gmFunctions on the fly. Since everything is a member, there are no globals and you can call member functions by known name on an instance as needed.

Is this closer to what you're after?


Top
 Profile  
Reply with quote  
PostPosted: Sun May 20, 2012 6:36 pm 
Offline

Joined: Sun May 13, 2012 4:16 pm
Posts: 5
Here's a diagram of what I'm trying to do (at least this is what I would do if I wrote my own little toy language):

BTW, I've read the documentation that came with GM, and looked through some of the C code, but still for the life of me can't figure out what I'm supposed to do to even compile a script (bar the method described in the tutorial, which I cannot use). Perhaps the problem lies in the fact that I'm thinking about the language in the wrong terms. It's seems very generic, designed to be useful to a wide variety of clients, but in my case, the hassle involved in getting it to work is slowly becoming farcical.

I'd take a leaf out of SFML's book in doing user-friendliness. I've had little trouble getting that system to work for my needs, and their documentation is utterly supreme. It really facilitates getting things working quickly.


Attachments:
example.png [14.64 KiB]
Not downloaded yet
Top
 Profile  
Reply with quote  
PostPosted: Mon May 21, 2012 11:23 am 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 698
Yes, there seems to be some misunderstandings going on. If you are willing to persist, hopefully myself and other forum members can make helpful suggestions.

I looked at your diagram. Here's some comments:
o C++ functions bound to script - This is expected normal usage, gmScriptEx and gmBind can help with C++ binding. Note that script bindings use a C style callback so they may need extra routing to C++ class instances. Often applications implement their own reflection system which simplifies conversion from messages, schedulers, remote calls and also scripting.
o Machine represents the script virtual machine.
o Object templates - not sure the format, but default functions, generic functions or templates where parts are actually replaced are all possible. GM can help here depending on what you have in mind.
o Compiled and non-compiled script - Sure, compile to byte code in advance or execute strings (which internally runs a compile phase).
o Object instance with UIds, local vars, event handlers. - Event function calls are a great use of script (as opposed to writing great chunks of time critical code in relatively slow script). Object instances can be specialized with GM. Local variables though? What do you want to do with them? In GM, local variables are local to the current function and not accessible outside. This can be problematic and hard to understand vs C/C++ and some other scripts. Unfortunately GM does not implement closures so scopes to not work like C/C++. For 'local' to object variables, the best thing is to use 'member' variables, which look like 'this.someVar' or '.someVar'. It is good practice to avoid the global table in GM apart from perhaps a handful of true globally accessible things.


Further resources:
Compile to byte code: viewtopic.php?f=5&t=65
Articles/Tuts: viewtopic.php?f=14&t=160 (Not sure if this is the tutorial you mention reading.)
GM ScriptEx + gmBind: viewtopic.php?f=14&t=422


Top
 Profile  
Reply with quote  
PostPosted: Tue May 22, 2012 3:06 pm 
Offline

Joined: Sun May 13, 2012 4:16 pm
Posts: 5
What I need to do in steps:
* Figure out how to compile several separate scripts, and call functions from within them
* Bind C++ functions to be callable from within any executed GM script function (I'll worry about this later)
* Create a C++ type called Thing which is reference to a game world object (containing base pointer) and can be passed around in GM script functions, but not created directly on the script side (the internal data should be inaccessible)


This is how far I've got from your examples:

Code:
#include <gm/gmThread.h>
#include <gm/gmStreamBuffer.h>

int main()
{
    gmMachine gm;
    gmStreamBufferDynamic buffer;

    const char* scriptChars = "f1 = function() { print (\"f1 called\"); };\
                              \
                              f1();";

    int numErrors = gm.CompileStringToLib(scriptChars, buffer);
    gm.ExecuteLib(buffer, NULL, true);

    if (numErrors) printf("compile error");
    printf("Press enter to exit");
    getchar();
}


Now I just need a way to call f1() directly, when executing the script. I honestly don't understand why there isn't an obvious means of calling a function within a script from outside of the script.



Lastly, I could do the following, and have a script for each C++-side event:

Code:
    const char* scriptChars = "function() { /* event function body */ }();";


However, that would mean having to manage a large amount of scripts, which is undesirable. I'd rather just have one script per game world entity.


Top
 Profile  
Reply with quote  
PostPosted: Wed May 23, 2012 3:59 am 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 698
You are not going to be able to call a local function from within a script, from outside the script. The function must be either global or member.
So here's what I'd do... You need a way to execute script 'on' the Thing and execute functions on the Thing.
Something like this:

Minimal version...
Code:
// Execute String on (this) Thing
machine->ExecuteString(string, NULL, true, NULL, &gmVariable(thisThing->GetGMUserObject()));

// Call member function on (this) Thing
gmCall call;
if( call.BeginTableFunction(machine, funcName, thisThing->GetGMTableObject(), gmVariable(thisThing->GetGMUserObject()) )
{
  call.End();
}

More like production code...
Code:
bool ExecuteStringOnThisThing(gmMachine* a_machine, Thing* a_thisThing, const char* a_string)
{
  ASSERT(a_machine);

  gmVariable thisVar;
  thisVar.SetUser(a_thisThing->GetGMUserObject()); // the gmUserObject associated with C++ game object 

  int threadId = GM_INVALID_THREAD;
  int errors = a_machine->ExecuteString(a_string, &threadId, true, NULL, &thisVar);
  if (errors)
  {
    // log any machine error messages
  }
  // note, monitor MC_THREAD_CREATE to associate thread with Thing if needed

  return true;
}

bool ExecuteMemberFunctionOnThisThing(gmMachine* a_machine, Thing* a_thisThing, const char* a_funcName /*eg. a_retValue, a_param1, a_param2*/)
{
  ASSERT(a_machine);

  if( !a_funcName || !a_funcName[0] )
  {
    return false; // Requires function name
  }

  gmVariable thisVar = gmVariable::s_null;
  if( a_thisThing )
  {
    thisVar.SetUser(a_thisThing->GetGMUserObject()); // the gmUserObject associated with C++ game object
  }

  gmCall call;
  if( call.BeginTableFunction(a_machine, a_funcName, a_thisThing->GetGMTableObject(), thisVar) )
  {
    // eg. CallAddParam(call, a_param1);
    // eg. CallAddParam(call, a_param2);
    call.End();
    // eg. CallGetReturn(call, a_retValue);
    // note, monitor MC_THREAD_CREATE to associate thread with Thing if needed
    return true;
  }
  return false;
}

Now you can write code like:
Code:
ExecuteStringOnThisThing(myMachine, myThing, ".f1 = function() { print (\"f1 called\"); };");
ExecuteMemberFunctionOnThisThing(myMachine, myThing, "f1");

Note that as per the GameObject example, your C++ Thing will have a gmUserObject and a gmTableObject (or similar behaving class) associated with it. The table object is a convenient way to allow script side member data & functions to be attached by passing through via operator dot.
Also note the ".f1 = function(..." or "this.f1 = function(...", we are setting it as a member function for convenient, non global access.
Final note, the comment 'associate thread with Thing' is for advanced use where you want control over script threads relating to a Thing. For example, when a Thing is removed, you may want to terminate all associated threads.


Top
 Profile  
Reply with quote  
PostPosted: Thu Nov 22, 2012 5:51 am 
Offline

Joined: Sun May 13, 2012 4:16 pm
Posts: 5
Here's what I need.

First let's say I have a single script file that contains many functions:

Code:
event_1() // possible entry point
{
}

event_2() // possible entry point
{
}

event_3{} // possible entry point
{
}

non_event_func()
{
    // might get called from any of the event funcs
}

Now, let's say I have a game world object that stores this script. The player may interact with the object causing an event to fire. Now I need to call a function from within that object's script. Let's say the player walks into the object. Now I need to call the onBump() event function.

Here's how I'd do it ideally:

Code:
int RV = executeFunctionWithinScript("onBump");


See how simple this could all be if only you didn't impose daft restrictions on clients? Is it possible to get this behaviour or anything near?

Thanks.


Top
 Profile  
Reply with quote  
PostPosted: Thu Nov 22, 2012 10:53 pm 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 698
Truncheon wrote:
Is it possible to get this behaviour or anything near?

Sure. It's intended to be used like this.

If your script functions are uniquely named, make them global and call something like:

Code:
ExecuteGlobalFunctionOnThis(someObj, retVal, "SomeFunc", param1, param2);


Your script file would look like
Code:
global SomeFunc = function(param1, param2)
{
  return blah;
};


Of your script functions are the same name but different between script files or dynamically assigned, you would call something like:

Code:
ExecuteMemberFunctionOnThis(someObj, retVal, "SomeFunc", param1, param2);


Your script file would look like
Code:
.SomeFunc = function(param1, param2)
{
  return blah;
};


Those native functions like ExecuteGlobalFunctionOnThis, ExecuteMemberFunctionOnThis are just wrappers around gmCall you write for convenience. Since C++ can't overload return values only, my example shows values returned by parameter. The native return value might be a call success state.

Many people take this two steps further:
1) Overload wrappers like ExecuteMemberFunctionOnThis to take 0-n parameters.
2) Use native reflection or call binding to call C++ member functions from script and vice versa.
These ideas are common whether you're binding GM, Lua, Python, or any other script.
Some take it one further step with automatic binding, but that often requires parsing native and/or script code to auto register available functions and types.

Here's an example call wrapper from some of my old code:
Code:
bool Script::ExecuteMemberFunctionOnThis(Entity* a_thisEntity, CallEventParam* a_retValue, const char* a_funcName,
                                              CallEventParam a_param1, CallEventParam a_param2, CallEventParam a_param3, CallEventParam a_param4)
{
  if( !a_funcName || !a_funcName[0] )
  {
    return false; // Requires function name
  }

  gmVariable thisVar = gmVariable::s_null;
  if( a_thisEntity )
  {
    thisVar.SetUser(a_thisEntity->GetIScript()->GetUserObject());
  }

  gmCall call;
  if( call.BeginTableFunction(m_machine, a_funcName, a_thisEntity->GetIScript()->GetTableObject(), thisVar) )
  {
    CallAddParam(call, a_param1);
    CallAddParam(call, a_param2);
    CallAddParam(call, a_param3);
    CallAddParam(call, a_param4);
    call.End();
    CallGetReturn(call, a_retValue);
    CallAttachThreadToEntity(call, a_thisEntity);
    return true;
  }
  return false;
}

Example usage:
Script::ExecuteMemberFunctionOnThis(someObj, NULL, "OnEnterTrigger", otherObj);

Example usage:
CallEventParam numEnemies;
Script::ExecuteMemberFunctionOnThis(someObj, &numEnemies, "HowManyEnemiesNearby", 10.0, 4.3);
print("numEnemies = %d", numEnemies.int);


In the above code, 'Entity' is a game object base type. 'CallEventParam' is a simple boxing class which overloads construction from simple types like int, float, string, Entity and stores the value in union. 'CallAttachThreadToEntity' is a helper to associate the newly created script thread with the game object so, for example, if the object were to be killed, we could say 'kill all threads for this object' and make all behavior end immediately. 'CallAddParam' is a simple wrapper on gmCall and just calls gmCall.AddParam* based on CallEventParams stored value type.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group