Sign in to follow this  
  • entries
    191
  • comments
    861
  • views
    116840

Sigh

Sign in to follow this  
Mithrandir

126 views

My awesome scripting system has a flaw, unfortunately.


Constructors...



So what happens if there are scripts that need constructors? It's easy enough to pass in an array of parameters, true; reflection handles that quite nicely.

The problem exists when scripts are reconstructed, due to there being a new version of a script. The easiest solution would be to keep track of the arguments passed into the original constructor, and pass those same arguments in again.

Of course, this isn't really such a grand solution, because of several things.

1) data that was valid when the script was originally constructed may not be important anymore. Say a script needed value X, and then during the course of executing, X changed. Now if you reconstruct the script as a new version, and pass in the old value of X again... well, you're kind of screwing things up!

2) There may be situations where the parameters to the constructor completely change.


So, I'm thinking, my scripting engine should just disallow constructors. There's really no easy way to handle this situation.


Now... the tricky stuff.


Using reflection to automatically copy details from one class to another is a hack, but it's the only hack I can think of. Why is it a hack? Well, because it ignores all the rules. For example, reflection can get private types, protected types, etc, and modify them on a whim, even if you really have no right to be touching that crap. Oh and the worst part is? I've successfully managed to overwrite a "readonly" variable. Ouch. How's that for code safety?


So why is this important?


Well, for example, there are times when this behaviour is desired, and times when it's not.

The best example of how ignoring the language safety features is when loading an object from disk. The loader is going to want to set the raw values of data, without worrying about pesky properties messing things up. For example:


public string Password
{
get { return password; }
set { password = Hash.SHA512( value ); }
}




The password of a player is never actually stored anywhere in the program. When you set the password, the account class smartly converts it to a 1-way hashed representation of the value.

If we try to set the password through the property, using the loaded value from disk (the hash), then the password will be completely messed up, because you're hashing the hash. This is not really a good thing.

So we need the ability to directly set the value, without worrying about protections and such. There are plenty of other uses.



Anyway, back to the topic; entity logic scripts in BM2.0 will be linked to entities. When you register a script with an entity, it will hook itself into the appropriate events (which aren't actually events, but dynamically-multicasted delegates stored in a dictionary based on event name), so the logic script is automagically notified whenever an event affects an entity.

Logic scripts cannot be transfered between entities, so their entity pointer should not be changable. Meaning that it should be a readonly value. Unfortunately, readonly values can only be set in a constructor, or hacked by using reflection, and if we can't use constructors...

oy, going in circles :)


So, I could make a stipulation in the scripting engine that says "You may use constructors, but make absolutely certain that the values never change!". I hate that. I don't want to voluntarily cripple my code for a specific instance! Plus, another problem is that I would need to require a parameterless constructor (for cases where the parameters aren't know beforehand, such as when loading a script from disk), which makes the users job harder by requiring 2 constructors, since C# doesn't allow constructor inheritance.

Or I could make entity pointers in the logic scripts not read-only... and face the potential of malicious (or incompetent) coders screwing up the game. No thanks.

So that leaves me with... using reflection.


The scripts won't have access to reflection, so I don't have to worry about scripts messing things up on purpose.

The problem is ultimately, constructors are a bad idea, because in two out of the three cases where they may be needed (1) loading from disc, 2) loading new version) we aren't actually "constructing" the object (technically we are), but merely loading a previously constructed state into a new object.


Sooooo, I think the best solution I can think of is to have a function, that acts kind of like a constructor, but is called on a script object once its data has been loaded. A function that says "Your data is set, now perform everything you need to do in order to put yourself into a valid operating state".


The three situations, then would be performed like thus:


1) Creating a brand new logic
1.1) Create logic (script engine)
1.2) stuff entity ID into it (game engine)
1.3) call the "Register" function (game engine)

2) Updating old logic with new version
2.1) Create new logic (script engine)
2.2) copy data over (script engine)
2.3) unregister old logic (script engine)
2.4) register new logic (script engine)*

3) Loading a logic from disk
3.1) Create new logic (script engine)
3.2) copy disk data into logic (game/file engine)
3.3) register logic (game engine)


* - I may change the order of 2.3 and 2.4, since registering the new logic may throw, and in that case, an unstable state may exist in the program.
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now