- 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.
>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
> 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.
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
};
> 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 "."?
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?
>All properties are set as static constants (with a prefix):
Isn't the constantness of the DefCore the reason to abolish it?
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.
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.
> 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.
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
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.
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;
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.
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.
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.
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.
>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.
> 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.
func Serialize(s)
{
s.X = X;
s.Y = Y;
s.SetTurretDir(dir);
}
P.S: Hey, you are back in Germany??
> 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.
The defcore itself is never saved, of course.
Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill