Not logged inOpenClonk Forum
Up Topic Development / Developer's Corner / C4Script for everything
- - By Günther [de] Date 2009-05-19 23:12 Edited 2009-06-03 11:56
Some of the most serious inconveniences when developing an object for Clonk are the definition of Actions in the ActMap.txt, and the inability to change them and the defcore.txt settings at runtime. It's time to change that. Here's my proposal:
- Introduce a new data type, tentatively named proplist. It's a simple map of strings to c4values, designed so that it can be implemented quite fast if necessary. It can have a special property named Prototype, which is used to look up a property in a parent proplist if it is not defined in a given proplist.
- C4Objects and C4Defs inherit from C4Proplist, and every object needs to have a proplist which is a C4Def in it's prototype chain
- Most if not all properties which are now set in the DefCore.txt and the ActMap.txt are reimplemented as proplist properties, and the engine looks them up that way. That means they can be changed for every object, and you can create new definitions by creating a new proplist which inherits from a C4Def and overwriting some properties.
- Saving C4Proplists should be easier than saving a C4Def, which means that the main problem with "SetDefCore" can be solved.
- C4IDs are replaced by (pointers to) proplists

I've pushed a branch which implements some key parts of the above to http://bitbucket.org/guenther/openclonk. You can simply "pull" from the url given there to your openclonk.org checkout, but take care not to push those changes to hg.openclonk.org if you have commit access there. (I think the default push action is to push them!)
You can already change the ActMap of a defintion by SetProperty("Some Action property", newvalue, GetProperty("Action Name", GetProperty("ActMap", C4ID)));

The biggest to do items are savegame support, and conversion of remaining C4DefCore properties. And some design choices need to be changed:
How should the C4Script integration look like? At the moment, I added some functions (CreatePropList, GetProperty, SetProperty), and a proplist literal syntax ({ key = value, key = value} creates a new proplist, value can be an arbitrary c4script expression), but those are clearly suboptimal. Ideally, one could set the prototype with a nicer syntax than { Prototype = GetDefinition(42), [...] }. Named local variables should be implemented as properties, and a syntax to access properties of other proplists is needed.
How to set the properties of the proplists created with every C4Def? Or can we get rid of them entirely and manage the Graphics and Script functions some other way? (At the moment, I've added a callback which needs to use SetProperty().) How do we avoid saving every definition into a savegame?
Using one big ActMap proplist and the old SetAction(string) API is suboptimal. Perhaps we should replace it by some script which simply sets the relevant properties of an object, but the abstraction of named activities does have some value.
Reply
Parent - - By Newton [es] Date 2009-05-24 12:32 Edited 2009-05-24 12:34

>It's a simple map of strings to c4values


What's with for example vertex lists? What C4Value type whould that be? Is there a way to initialize arrays like this = {1,2,3,4} already? Otherwise I can't see how this can work.

>- C4Objects and C4Defs inherit from C4Proplist, and every object needs to have a proplist which is a C4Def in it's prototype chain


1. Do you think the prefix C4 for those is razonable?
2. How does inheriting work? Would it be possible to have objects which do not inherit from C4Object/C4Def, which one can not create in the game but use for inheritance? (-> Svens OOP suggestion)

>- C4IDs are replaced by (pointers to) proplists


How does this look like?

>How should the C4Script integration look like


How about this. All properties are set as static constants (with a prefix):

object Rock : C4Object {

// override standard definition set
// default values have been defined in C4Object
// this would require that one can redefine static (constants) in inherited objects
static defWeight = 10;
static defWidth = 5;
static defHeight = 5;

// new values like defIsWeapon could be added easily:
static defIsWeapon = true;

func Hit() {
  // access is as easy:
  if(defWeight > 5) { ... }
  // access to defs of other objects is easy
  if(CLNK::defWeight < defWeight) { ... }
}

};


Why like this? Because I don't see a reason why engine-used properties should be seperated from user-used properties in their syntax. And, I think this is the easiest and most intuitive way. Of course there would be no proplist then. Actions would have to be defined as actual maps like

static actWalk = {
Procedure = WALK,
FacetBase = 1,
Delay = 1,
...
};


, set by SetAction(actWalk) and accessed like C4ID::actWalk.Procedure
Parent - - By Günther [de] Date 2009-05-24 13:32

> What's with for example vertex lists? What C4Value type whould that be?


An array would be the most obvious :-)

> 1. Do you think the prefix C4 for those is razonable?


"reasonable"? Every class has C4 as a prefix. Uniformity is important.

>>- C4IDs are replaced by (pointers to) proplists
> How does this look like?


At the moment it's almost imperceptible by the scripter - Definitions still have an id, and id literals in the script still work if a definition with that id is present. They just evaluate to a pointer to the definition (with c4value type proplist). All engine functions which took an id get that id now from the proplist. Most should be converted to directly operate on a proplist instead.

> 2. How does inheriting work? Would it be possible to have objects which do not inherit from C4Object/C4Def, which one can not create in the game but use for inheritance? (-> Svens OOP suggestion)


Yes, that's the whole point. The technical term is prototype-based programming.
Reply
Parent - - By Newton [es] Date 2009-05-24 20:15
If the properties can be changed, what about (~same syntax):


object Rock : C4Object {
// the word Rock defines the ID. It can only be created by CreateObject
// if it inherits from C4Object which defines the standard definition set

// already defined
defWeight = 10;
// new
static defIsWeapon = true;

// access via Rock::defWeight
};
Parent - - By Günther [de] Date 2009-05-24 21:32

> access via Rock::defWeight


I don't like "::". The only reason to use it would be because C++ uses it, but C++ has an exceptionally ugly syntax. If it didn't risk confusion with "->", I'd vote for ".". Hm, dare we change that to "."?
Reply
Parent - - By Newton [es] Date 2009-05-24 22:03 Edited 2009-05-24 23:52
Were we not using exactly C4ID::obj->Call() or something before?

Actually it is "." in Java. So a dot would match with the Java syntax - Math.PI <=> Rock.Weight

So perhaps we can go to this:
var foo = FindObject();
foo.GetDir();

and
if(Clonk.defRotate == true) ...

It doesn't cause confusion in Java, does it?
Parent - - By Carli [de] Date 2009-05-25 09:05
i like the '.'-operator. C4Script does not use pointers so we do not need to have '->' _and_ '.'. Only '.' would be easier to learn
Parent - - By Kanibal [de] Date 2009-05-25 13:17
Imho, the "->"-operator makes it more clear, what you mean. And it looks better, too :)
Reply
Parent - By Enrique [de] Date 2009-05-25 13:28
Yep.
Reply
Parent - - By Newton [es] Date 2009-05-26 01:34
-> is more suggestive, thats true. But I think this is a matter of taste. I, for instance like the . more because the code is more readable to me at the end.
Parent - By LoneS [fi] Date 2009-05-26 17:52

>I, for instance like the . more because the code is more readable to me at the end.


I'm with you on this one.
Parent - By Sven2 [us] Date 2009-05-26 18:58
I'd also prefer "." to "->"; mostly because it's shorter which makes it easier to type.
Parent - By Günther [de] Date 2009-05-25 18:48

> Were we not using exactly C4ID::obj->Call() or something before?


One more reason not to use :: for this ;-)
Reply
Parent - By Newton [es] Date 2009-05-24 20:19

>Yes, that's the whole point. The technical term is prototype-based programming.


CreateObject(superHeavyRock : Rock { defWeight = 10000; }); ?
Parent - - By Caesar [de] Date 2009-05-24 14:44

>All properties are set as static constants (with a prefix):


Isn't the constantness of the DefCore the reason to abolish it?
Parent - By Günther [de] Date 2009-05-24 16:38
Yeah, that syntax does not suit prototype based programming. I had hoped that newton would pick that up from the link and propose something else ;-)
Reply
Parent - - By Newton [es] Date 2009-05-24 19:58
Is it safe to be able to change any defcore properties at runtime?
Parent - By Günther [de] Date 2009-05-24 21:26
For most, the only reason is savegames. That might very well be also a reason to still have constant objects, to avoid having to save those in savegames. One could still use inheritance to make any desired changes.
Reply
Parent - - By Carli [de] Date 2009-05-25 09:10
When do everything by script, the game would become very expensive in executing scripts bytecode.
I could write a compiler that generates inlined x86-opcode from C4-Bytecode. It could be faster and is as secure as the old version. For compatibility f.e. PowerPC or future OS with VirtualProtection we could make this feature defeatable.
Parent - By Sven2 [us] Date 2009-05-25 09:29
I'm not sure whether opcode would actually be faster. You only save the time of one switch, but introduce lots of new code segments. It could be significantly slower than the interpreted execution.

In any case, I suggest you do not start optimizing until the bytecode execution has actually proven to be a bottleneck. A better place to optimize would be graphics, because they're the number one issue causing laggy games in CR.
Parent - By Günther [de] Date 2009-07-21 16:13

> I've pushed a branch which implements some key parts of the above to http://bitbucket.org/guenther/openclonk.


I merged this branch now. Savegames are still kind of broken, but that can be fixed in the main branch.
Reply
Parent - - By Atomclonk [de] Date 2009-07-21 21:13
Everything seems to me a bit stupid... At first (I didn't read the whole text and answers) do we want to change the DefCore entrys for all objects of the same ID or just for a single one?
Why things like a proplist-thingie, when we could do ist like this:

SetDefCoreValue(idID, szValueName, iNewValue, iNumber);
idID: ID of the objects to change. (or a single object like pObject)
szValueName: Name of the DefCore entry.
iNewValue: New value.
iNumber: Which value of the entry has to be changed (beginning with 0).
I have to think about, how to handle strings and IDs... (any propositions?)

SetActMapData(idID/pObject, szDataName, iNewValue);
... oh, forget it, I (and maybe many other people) were never interested in appending things to the ActMap, because when you change it, mostly because of changing graphics, and then you need to copy the whole object.

And please excuse me, if I've missunderstood something. :S
Reply
Parent - - By Günther [de] Date 2009-07-21 21:45
Changing the actmap is useful during development, and there are probably some nice tricks you can do with a dynamic actmaps.

The main reason for the new feature is to consolidate all the different functions you have to use to manipulate objects. Some things you can change with a callback, some things you can change with the scenario.txt, some things have a dedicated function, others can only be changed by editing the game data. And for most there needs to be special code in the engine to (de)serialize it for savegames.

I'm not yet sure whether the proplists created for the definitions will be changeable during the game. I'd like to avoid having to write code to merge the changes done during the game with changes to the game data between saving and resuming the game. So you'll probably have to change the objects, or create a proplist inheriting from a definition at runtime and have your objects use that proplist as their definition.
Reply
Parent - - By Atomclonk [de] Date 2009-07-21 21:53
Oh, ok, now I understand. But couldn't this be done with the Objects.txt?
Reply
Parent - - By Luchs [de] Date 2009-07-22 12:13
Objects.txt should also be removed, as it often breaks scenarios.
Parent - - By Atomclonk [de] Date 2009-07-22 12:51
And what shall we use for saving things, like the Objects.txt did?
Reply
Parent - - By Kanibal [de] Date 2009-07-22 13:39
Script.c/CreateObject()
Reply
Parent - By Atomclonk [de] Date 2009-07-22 14:05
Not every object may be serialized like in Hazard.
Reply
Parent - - By Clonkonaut [de] Date 2009-07-22 14:07
That's not this easy. Look at this:
var obj = CreateObject(...);
obj->SetController(...);
obj->SetClrModulation(...);
obj->SetGraphics(...);
obj->SetObjDrawTransform(...);
obj->Incinerate();
obj->SetCon(...);

In addition every single object need a Serialize()-function (like in Hazard) to do this:
obj->LocalN("foo") = bar;
Reply
Parent - By MrBeast [de] Date 2009-07-22 17:10
I would like to have something like:
var szObj=GetObjString(pObj);
CreateObjByString(szObj);

An Function which returns a string which can be to an new object. May be with some aditional parameters which defines what will be saved. For examle that locals and effects are optionally not saved.
Reply
Parent - - By Sven2 [de] Date 2009-07-22 18:46
...and in the end, you have gained zero extra upwards compatibility, because you just introduced another syntax for Objects.txt.

The main causes for incompatibility in the past were either renewed graphics (e.g., when the entrances of the dark castle moved) or renewed scripting (locals renamed/changed from Local(), etc.). Neither would be fixed with a scripted approach.

Of course you could add functions in the object to resolve incompatiblities. But then, the same can be done for Objects.txt saving.
Parent - - By Newton [es] Date 2009-07-22 19:15
The Serialize function of course only saves the stuff that itself deems necessary. The Graphics, the actions, the dimension, the local variables etc can change and it still works. So, instead of saving the variables, Serialize tries to gain upward compatibility by using its own interface e.g. this:
func Serialize()
{
  var evalstr = Format("SetTurretDir(%d);",dir);
  return evalstr;
}
func SetTurretDir(int d)
{
  dir = d;
  // SOME MORE STUFF!
}


If the SOME MORE STUFF involved setting actions, graphics etc. and these changed in the next version, the objects.txt is broken because in the objects.txt dir=-1, action=hurzel, phase=3 etc is saved.
Parent - - By Sven2 [us] Date 2009-07-22 20:21 Edited 2009-07-22 20:26
You put a lot of work on the object designer. But I guess we could get away with some objects losing their action if the author was careless.

By the way: It's very common to design your scenarios using custom parameters the author did not think of. The most common ones are DrawTransform, ClrModulation, BlitMode; sometimes even Category. A set of standard parameters would have to be serialized in the base object.

Also, I don't like the syntax of the serialize function. I would prefer if simple properties could be auto-serialized by marking the local variable as such, and if there were a simplified way to build the construction string. e.g.:


stored local owner; // saved automatically
local X, Y, dir; // saved manually

func Serialize(s)
{
  if (!_inherited(s, ...)) return false;
  SaveProperty(s, "X");
  SaveProperty(s, "Y");
  SaveProperty(s, "dir", "SetTurretDir"); // calls SetTurretDir(*) instead of dir=*.
  return true;
}


I'd hate having a Format-nightmare in *every* object definition.
Parent - By Newton [es] Date 2009-07-22 20:33

>You put a lot of work on the object designer.


Yes, thats why I and Clonkonaut? agree that Serialize is not really the non-plus-ultra here.

Even with an easier syntax like you suggested I wouldn't really like to see serializing in the object scripts because even with a more clear interface, it is still lots of work put into the hands of the object designer.
Parent - - By Günther [de] Date 2009-07-23 00:15

> You put a lot of work on the object designer. But I guess we could get away with some objects losing their action if the author was careless.


Well, no. It's way more important that savegames always work when the game data hasn't changed than that they work when it does. That's just a nice to have, cannot be solved entirely anyway, and restricting changes to a stable version would mostly eliminate the problem. So we should only consider solutions that have a low cost.
Reply
Parent - - By Sven2 [de] Date 2009-07-23 11:04
Savegames should use the traditional saving method anyway. The new method could be used by scenario designers ("Save as scenario").
Parent - - By Newton [es] Date 2009-07-23 11:13
I think the most intuitive way for a serialize function would be like a copy constructor. Something that looks similar to:

func Serialize(s)
{
  s.X = X;
  s.Y = Y;
  s.SetTurretDir(dir);
}


P.S: Hey, you are back in Germany??
Parent - By Sven2 [de] Date 2009-07-23 14:07
This would require some weird compiler magic. What kind of object would "s" be? It would have to convert all operations on itself into script code. Using SaveProperty-functions, s could just be a reference to a string.

> P.S: Hey, you are back in Germany??


Yes, for the time being. My time is very limited though, and I might go back to the US next year.
Parent - - By Günther [de] Date 2009-07-23 00:20
And the problem with graphic changes is relatively easy to improve: we just need to stop saving the Shape when it hasn't changed.
Reply
Parent - - By Newton [es] Date 2009-07-23 09:43
In general: Parts of the defcore, shape etc. are saved even when it has not been changed?
Parent - By Günther [de] Date 2009-07-23 11:14
Well, it's a bit more complicated: They aren't saved when they haven't changed from the default, it's just that the default for the Shape is something useless like 0,0,0,0, and not what the definition has. I'm also not sure why an object needs the shape at all (apart from SetShape, which clearly wasn't the original reason). Perhaps matthes wanted to avoid recalculating rotated shapes or something.

The defcore itself is never saved, of course.
Reply
Parent - By Günther [de] Date 2009-07-22 12:57
Proplists will probably be saved in the objects.txt.
Reply
Up Topic Development / Developer's Corner / C4Script for everything

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill