GameMonkey Script

GameMonkey Script Forums
It is currently Tue Mar 19, 2019 7:43 pm

All times are UTC




Post new topic Reply to topic  [ 9 posts ] 
Author Message
PostPosted: Fri Jun 19, 2009 12:52 pm 
Offline

Joined: Fri Jun 19, 2009 12:39 pm
Posts: 31
I'm about to start using GameMonkey with my rtti/property system in my game engine. I'm pretty clear on the requirements, but I have a quick question about the language itself.

I notice that functions are essentially a type of variables which then have a body beneath them. I want to be able to create an instance of a class in GM, and then set certain members in the instance after creation. So the code would be something like

Code:
myInstance = MyClass
{
    SetName("Something");
    member_var1 = 50;
    memver_var50 = 100;
};


Is this at all possible in GM? If not, is there an alternate method for doing something this, apart from using some sort of constructor?

The reason is that I would like to use GM for defining object properties either by hand or in an editor. I'm currently using XML files to do this, which create objects by type and name, and can then set any properties exposed to the system. They can even link to other other objects by name, assuming that they have been created in the correct order.

I would like to have ONE unified system for creating named objects, setting properties and scripting. But if GM can't do this without major changes, then I can live with the two systems working along side each other.

Actually...the more that I think about it, I would REALLY like to do the following

Code:
myInstance = myClass as UniqueName
{
    // Optionally define member values here
};


with the "as UniqueName" part an optional part of the syntax.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jun 19, 2009 2:32 pm 
Offline

Joined: Fri Jan 14, 2005 2:28 am
Posts: 439
With the default syntax the options are pretty limited.

You could use a syntax that I've used before in C++ when I have a number of parameters where I only generally want to set one or 2 from their default, and didn't want to provide the parameter list every time. The syntax isn't terribly pretty, but it basically involves a bunch of modifier functions returning a reference to 'this', so you can do stuff like this.


Code:
obj = CreateEntity("badguy01").SetName("somedude").SetHealth(100);


This relies on function based modifiers rather than setting variables directly though, which may be less than desirable.

What I've seen done in lua in order to attempt to be object oriented is setting things up so that the constructor simply takes a table, and it can be selective about what properties you set on it.

Code:
obj = CreateEntity("badguy01", { name="somedude", health=100 });


Top
 Profile  
Reply with quote  
PostPosted: Fri Jun 19, 2009 2:51 pm 
Offline

Joined: Fri Jun 19, 2009 12:39 pm
Posts: 31
The table syntax might be ok, but I'm not sure how that would work if I had a property that is a either a statically allocated or dynamically allocated array.

But the table based solution does look good...I can provide a general CreateObject() method that takes a name, and perhaps a name parameter list as a table, which while not as pretty as my preferred solution, might do the trick for now.

Once I'm more experienced with GM, I might poke around the parser and VM to add the desired functionality. I've done something like this for my day job, so I'm not expecting it to be impossible.

Thanks, Dr Evil!


Top
 Profile  
Reply with quote  
PostPosted: Mon Jun 22, 2009 4:17 pm 
Offline

Joined: Thu Jan 01, 2004 4:31 pm
Posts: 307
Been thinking about this and think that I could modify GM to handle this sort of use.

There's a few ways to do this; one is to implicitly pass all the intialisation parameters to the constructor function.

So:

Code:
x = myclass( x = 3, y = 7, z = 6 );


Would basically go:

Code:
PushString("x");
PushInt(3);
PushString("y");
PushInt(7);
PushString("z");
PushInt(6);
Call MyClass function binding


In your native-bound function callback, you'd have to check all the parameters in turn, possibly using some macros.

Code:
GM_GET_NAMED_PARAM_INT( "x", x );   // get "x" into C++ var named x
GM_GET_INDEXED_PARAM_INT( 8, x );  // get param at index [8] to var named x -- eg: from  x = func( [8] = 100 );


Quite ugly and could mean that you'd have to have separate functions for the calls that don't want named fields. I don't think that this would be very practical really.

A separate option would be to provide a new variable list available for reading by the callback that provides the parameter names. This way you can read off any values as normal but names parameters are labelled correctly; in reality it'd probably be a table allocated with the names per index.

Another way would be to implicity create your table for you, ending up with the same way as DrEvil specified so that using such syntax creates a table, fills it with your values and then passes it to the callback. This would be the easiest option and probably the best. We could either have it passed directly as a parameter, or have a separate ValuesTable() method on the thread to get at it.

If we were doing something like this, I'd like the ability to use the curly brace syntax too, perhaps with a "type" modifier.

Code:
x = { x = 1, y= 2, z = 3 } as MyType; // would call some default function associated with your type (needs change to type registration)
y = { x = 1, y= 2, z = 3 }; // table is implicit if no modifier used
t = { x = 1, y= 2, z = 3 } as table; // table can be explicit if needs be


Table would need to be ripped out of being recognised by the grammar and treated exactly like one of these functions. I'd have to put in a special-case to recogise that a table is being created and then let the CodeGenerator run down its usual paths and work as expected.

It'd be relatively simple, I think - we just have to be clear how we want it to work.

Oli


Top
 Profile  
Reply with quote  
PostPosted: Mon Jun 22, 2009 4:35 pm 
Offline

Joined: Thu Jan 01, 2004 4:31 pm
Posts: 307
Also, it may be possible to get close to your original syntax.

Code:
x = classtype
{
  x = 1;
  SetName("Bob");
};


We'd probably achieve it by calling the classtype function with no parameters to create the object, then execute the block of code immediately afterwards by treating all of the functions/variables as being in the member scope, not local. It feels a bit kludgy and should be constrained to only be able to do simple operations.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jun 24, 2009 2:03 pm 
Offline

Joined: Fri Jun 19, 2009 12:39 pm
Posts: 31
The "passing values in the constructor" approach is something that I wanted to avoid really. As you said, it would require a different constructor for sets of values when what I want is an easy to use, general purpose way of authoring values.

I'm actually thinking of trying this out at work, by quickly integrating GM into the object system for one of our games so that I can easily modify setups for different type of cars. I'm willing to go the table route for now to give it a go...anything is better than my slightly hacky config compiler, which uses a Flex/Bison generated parser and lexer.

The "assuming member scope" solution is the one that is closest to what I'm trying to do, but if I'm honest, I would rather that GM not have a hacky solution just to facilitate me. It's a very solid scripting system, and I wouldn't want to see any ill thought-out features diluting that.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 25, 2009 3:24 pm 
Offline

Joined: Fri Jan 14, 2005 2:28 am
Posts: 439
I think the table solution is the best in terms of flexibility, as it allows only explicit initialization of what you want to change, and leaves everything else default.

Code:
o = CreateObject("car", { Acceleration=10, Health=20 } )


It would be trivial to support this type of construction in a binding interface, I'll probably add support for something like this to gmBind2, where you can take advantage of the binding interfaces and in your constructor you can initialize members from the table, so that if your object had Acceleration and Health variables, they would be initialized as if you did o.Acceleration = 10, etc, and anything not bound directly would go into the extensible table for dynamic script defined properties, or if the object is not extensible it would need to error.

Something like this perhaps.
Code:
int gmfCreateObject(gmThread *a_thread)
{
    GM_CHECK_STRING_PARAM(objtype,0);
    GM_TABLE_PARAM(initializers,1,0);
    ScriptObject *obj = CreateObject(objtype);
    if(initializers)
    {
        if(!obj->FromTable(initializers))
            // error
    }
    a_thread->Push(obj->GetUserObj());
    return GM_OK;   
}


The benefit of this is that you don't need to add any special table parsing code to look for named fields and set their values, it comes automatically with the binding interface, and it also supports script extensibility, so any variable not directly bound will still end up on the objects extensible table for script reference later. Exposing new fields to construction is simply exposing those fields to the binding interface, as they would use the same data. It does require each concrete class on the game side to implement a bit of functionality, but that's pretty simple. It's relatively static too, so it could be hidden in macros. Would probably look something like this.

Code:
class ScriptObject
{
public:
    virtual bool FromTable(gmTableObject *a_initializers) = 0;
    virtual gmUserGcRoot<gmUserObject> GetScriptObject() = 0;
protected:
    gmUserGcRoot<gmUserObject> m_ScriptObject;
}

class Car : public ScriptObject
{
public:
    virtual gmUserGcRoot<gmUserObject> GetScriptObject()
    {
        if(!m_ScriptObject)
            m_ScriptObject = gmBind2::Class<Car>::GetGcRootObject();
        return m_ScriptObject;
    }
    virtual bool FromTable(gmTableObject *a_initializers)
    {
        return gmBind2::Class<Car>::FromTable(a_initializers);
    }
};


Basically the concrete class functions are only needed as they need to call the proper templated gmBind functions for the type, and since it's relatively static other than that you could either hide them in macros

Code:
class Car : public ScriptObject
{
public:
    SCRIPT_SUPPORT(Car);
};


or perhaps hide them in another templated class, so exposing your classes is simply deriving from ScriptConstructible<>

Code:
template<typename T>
class ScriptConstructible : public ScriptObject
{
public:
 virtual gmUserGcRoot<gmUserObject> GetScriptObject()
    {
        if(!m_ScriptObject)
            m_ScriptObject = gmBind2::Class<T>::GetGcRootObject();
        return m_ScriptObject;
    }
    virtual bool FromTable(gmTableObject *a_initializers)
    {
        return gmBind2::Class<T>::FromTable(a_initializers);
    }
};

class Car : public ScriptConstructible<Car>
{
public:
};


Top
 Profile  
Reply with quote  
PostPosted: Fri Jul 10, 2009 10:48 am 
Offline

Joined: Fri Jun 19, 2009 12:39 pm
Posts: 31
I just wanted to say that I've managed to work GameMonkey into one of our game platforms, where I can construct and configure vehicles using scripts.

I went for the table route, which, I must say is amazingly powerful and really, there's no need for any additional syntax or language extensions. I can actually create my blueprint/data objects by just calling a bound native function, which passes the type, name and an optional parameter to the table.

I'm considering also adding support for looking up tables that have the same name as my data object, and using this to set value.

Integrating GM to one of our platforms has so far, been a very enjoyable experience for me. The only negative aspect of what I've had to do, has been directly associated with the limitations of our own code base. GM itself has been very straight forward to work with, and it is really surprising just how well designed the language and VM interface really is.

I've not yet exposed game objects as user types, as there are some issues with GC, object ownership and some other things that I haven't worked out yet. I might expose our smart pointer type though, and that will allow me to access object members and maybe exposed methods via script.

A big thanks to everybody for their help, especially DrEvil. Thanks guys, you all rock.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jul 10, 2009 1:13 pm 
Offline

Joined: Thu Jan 01, 2004 4:31 pm
Posts: 307
Glad to hear you've sorted out how to initialise objects. I must say that I think DrEvil's solution was very smart and flexible, something I'll be looking to use more/recommend to others as well.

I agree about GM being easy to work with. It's the main reason I've stuck with it for all of my scripting needs over the past 4 years instead of going to Lua or Python. I just find it so much easier to embed and use without issue.


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

All times are UTC


Who is online

Users browsing this forum: No registered users and 3 guests


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