• Advertisement
Sign in to follow this  

Thoughts on saving state where scripts are concerned

This topic is 4669 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am currently working on a save-game feature, and saving the majority of the game state has proved fairly easy, except for a few issues. One issue is that we use scripts (custom scripting system), to perform actions and events. It should be noted that in our system only one script at a time may be running, but many others may be in a 'wait' state. When a script function is executed it happens 'all at once' (like in C++), however at any point in a script function, the script can be put into a Wait state, wherein it will wait until some event occurs (a timer time-out for instance). When that event happens it will trigger the script's execution again. It is because of this 'wait mechanisim' that we must serialize the state of the scripts into save-games (since somthing could be pending to happen, and if the game is loaded without knowledge of that, it wouldn't happen). Now, my problem isn't saving the state of our scripting system, that is simple as. -save all global variables For All Running Scripts{ -save script file name -save local variables -save script locks(these are the wait conditions) -save execution head position(this is the issue) } My issue is that, by having to save the execution head position (what 'line' of code the current script executed last) it means that if I modified the source script, by adding or removing some code, it would break the save-game. Example(psuedo command structure): assign a to 10 assign b to 20 call function1 <-instruction head at index 2 assign a to 30 asign b to 40 Example(after modification): assign a to 10 assign b to 20 assign c to 100 <-instruction head at index 2 call function1 <-should be at index 3 instead assign a to 30 asign b to 40 This does not seem like a good thing, and I can see it leading to many headaches =) Does anyone have and thoughts, or potential solutions for storing the state without making it so that changing a script's command length (number of commands) doesn't break it? All feedback is welcome, thanks =)

Share this post


Link to post
Share on other sites
Advertisement
I think you should just save the number of the line. Why would you want to change scripts after you have released the game?

Share this post


Link to post
Share on other sites
Well, there may be a patch(thought this is common to break save games so it isn't much of an issue)

but it is more a case of development, having gotten half-way through a long game, and having to change one thing in any script and have it void all save-games would be very counter-productive for testing.

I am having a hard time thinking about it now, but I am fairly positive there must be a way to express position in a list that grows down, wherein if you know,

last list length
new list length
last list position

then you can discover, new list position

the trick seems to be that items can be added and/or removed before or after the head position

Share this post


Link to post
Share on other sites
After some more thought, it seems that this is somthing I am just going to have to live with.

Even if I could find a way to keep track of where the new head position should be, the potential extent of modification to the script code, could cause potentialy unforseen issues.

so it is probably a good idea to throw an error when trying to deserialize a script if that script's command length has changed. it unfortunetly wont catch all errors (if somthing was removed and another thing added), but it should be enough to catch most of the issues of (why the heck is it doing that? Oh! I modified a script and used an old save-game) so that we arn't going around the preverbial table trying to solve a non-issue.

I am still interested if anyone has any alternative ideas, it is very possible (and probable) that I've overlooked somthing.

Thanks =)

Share this post


Link to post
Share on other sites
I think it's quite acceptable that older savegames might no longer work when the game is updated to a newer version. Anyway, some solutions I can think of are:

- Maybe have some locations in your game where no scripts are waiting, if this is at all feasible. Like, when another chapter/level/whatever is entered, make an autosave at the beginning of that.

- You could always save the whole script an interpreter is executing with its state, of course ;)

- A more complicated method would be to never change a released script, but instead install the updated script in parallel and give the old one a pointer to the new version so it can be switched when excution is finished.

Well, just a bunch of ideas...

-Markus-

Share this post


Link to post
Share on other sites
Hmm some good ideas there.

It seems that a solution to my problem would be to have the save-game include the compiled script itself, so even if the file was changed, it wouldn't reload that file, instead it would deserialize the old(and correct) script's commands.

this would increase the size of the save-game of course, but it would provide protection.

however, if you made a change to the script (during development), you would still need to blow away the save game, since you wouldn't make use of the changed script(you would use the one stored in the save-game).

so it seems the end result is the same, except it takes care of potential errors, at the cost of larger save-game size.

I will have to think on this, thanks =)

Share this post


Link to post
Share on other sites
I believe you can do it using Serializable Continuations. [1][2][3]

If you change the save games, write an old-new save game translator or use the file versioning system you obviously implemented in your file format. You used one, right? [grin]

Share this post


Link to post
Share on other sites
the serializable continuation stuff looks a little bit complicated =)

as for versioning, yes, we 'can' save a version number into a serialized file, however, this version isn't directly linked to anything, that is, when somthing is changed I would have to modify the version number myself.

it seems like the simplest way is simply to save out the instruction head index, and if that breaks save games then so be it =/

Share this post


Link to post
Share on other sites
Without knowing exactly how your scripting system works, I can't say I know of a good solution, but one thing that comes to mind, with a scripting language, is, not only saving the execution pointer, but also a hash (or something) of the contents of that line/command. That way, when you load up that save with a modified script, you search through it for the proper line, update your execution pointer, and continue. If you fail to find the line, throw an exception or something, or restart the script.

Now, while this is a simplistic idea, it's what pops to mind first.

Share this post


Link to post
Share on other sites
The problem is that you could change the script any amount, so any way you could possibly synchronize across changes might be invalidated.

What I would suggest would be some kind of 'diff' that is used to update save files whenever a patch is applied. This 'diff' file would have the information needed to tell the game 'ok, you were executing line X before, but now you need to execute commands _1, _2, and then pretend you were on line Y and resume from there'. This can't work for drastic changes (without a lot of work specifying the _1, _2, etc commands to execute before resuming), but it can allow mostly-automated updating from only slightly changed script.

Share this post


Link to post
Share on other sites
Hi,

I hope this hasn't already been said, I was too lazy to read all the answers. ;-)

Why do you have to save the line of the script? Is your scripting system interpreted directly from the script text file?

Because, this is one of the problems you do not run into when you compile your script text into some kind of binary token- or byte-code. When you save the state you just need to save the stack and program counter, along with the binary code.

Here are some suggestions:

- Consider to translate your script text into binary tokens or byte code and save the binary code with your save game. This will also improve execution speed, however it may be too much effort, depending on the complexity of the script language.

- Save a copy of the script text with the game state (you could 'cache' the scripts at the beginning of a game / level, or each time you actually save the game).

However, both suggestions would mean when the user loads an old save-game, and some of the scripts have been updated in the meantime, these updates will not be used. For this case you could run the 'cached' script until reaching a "safe-point", where it would be safe to exchange the 'cached' version with a newer version (for example when the script has run-through).

Hope this helps,
jewe

EDIT: I think for save games it is *crucial* that they do not break with an update of the game. Especially if you want to support user changeable scripts, I think it is a *must* that changes in scripts do not break saved games.

It is less important, I think, that changes to scripts immediately take effect also when loading saved games, I think users could accept that they need to start a new game / level in order to take advantage of modified scripst (at least better than breaking the saved game...)

Share this post


Link to post
Share on other sites
Quote:
Original post by jewe_org
Because, this is one of the problems you do not run into when you compile your script text into some kind of binary token- or byte-code. When you save the state you just need to save the stack and program counter, along with the binary code.


You run into exactly the same problems. Byte-code, or even machine code, is still just a list of instructions. If you change the instructions you can't expect to continue executing from the middle in any case.

And if you choose not to overwrite the scripts or bytecode blocks that are currently being executed, you can't guarantee that they are synchronised with other changes across the program. For instance, what if your old code accesses a global which you've removed in the update? You have a problem.

To the original poster, I'd suggest that if updating scripts is likely to be a problem - no point worrying if it's not - then you try and minimise the number of scripts which can be running concurrently. A simple analysis should be able to show which routines can be paused and which ones can never be paused. The latter are presumably safe to update, the former are not.

Note that if the only way to suspend your scripts is a single wait routine, then you can get around this by breaking up the function into several distinct functions, and substituting a call like javascript's 'setTimeout' for your wait calls.

Alternatively you can perhaps add some restrictions - eg. if you guarantee that no wait calls are ever removed from a script, and you state that local variables do not persist across them, then you effectively have the same situation as my previous suggestion. Then you just have to make sure you can resume directly after the appropriate wait call, even when the script size changes.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by Kylotan
You run into exactly the same problems. Byte-code, or even machine code, is still just a list of instructions. If you change the instructions you can't expect to continue executing from the middle in any case.


Of course you're right, what I meant is that you automatically have a (binary) copy of the script code, you're not running on the original file, so it's easier to cache this binary copy, so you're independent from what happens in the original script code from within the save-game.

Quote:
And if you choose not to overwrite the scripts or bytecode blocks that are currently being executed, you can't guarantee that they are synchronised with other changes across the program. For instance, what if your old code accesses a global which you've removed in the update? You have a problem.


Sure, if the program changes in a way scripts are no more compatible, but I didn't understand the question that way. I understood he wanted to change the scripts without breaking the save-game, which is what my suggestion would solve - but I admit it might not be the perfect solution.

Quote:
To the original poster, I'd suggest that if updating scripts is likely to be a problem - no point worrying if it's not - then you try and minimise the number of scripts which can be running concurrently.


How does this help him which line to continue if the script has changed? (The line in question could even have been deleted...)? As I understand it, this is even a problem when running a single script at all.

Cheers,
jewe

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
Quote:
To the original poster, I'd suggest that if updating scripts is likely to be a problem - no point worrying if it's not - then you try and minimise the number of scripts which can be running concurrently.


How does this help him which line to continue if the script has changed? (The line in question could even have been deleted...)? As I understand it, this is even a problem when running a single script at all.


Reducing the number of scripts that run concurrently doesn't solve the problem, but it does make it less likely that the scripts to be replaced will include one or more of the concurrently running scripts.

Share this post


Link to post
Share on other sites
Thanks to everyone who has presented ideas here.

It would seem there is no 'perfect' solution, there is no way to aleviate the case wherein an entire script file might no longer exist.

This entire problem seems to stem from the need to save the currently running scripts. If running a script happend 'instantly' and didn't have the potential to be waiting to continue, then this wouldn't be an issue.

Unfortunetly to eliminate this, would mean that i would have to write seperate scripts (or at least separate functions in a script) to handle (Now that something has happened what do I do next), which would be very hard to use, and was the whole reason that I implemented a scripting engine to begin with lol.

So for my final solution I have simply opted for the rule:

Any changes to game code or script code could potentialy break a saved state.

The only case where I don't see it breaking, is if you simply change any instruction in the script, adding and removing however are no-noes =D

Of course, even with this, it can be potentialy deciving to rely on a changed script to work properly, since if you change code that has already been run, the game will not act as if it ran it.

So, I have implemented the saving of the instruction head index, and it works fine, I just need to remember not to rely heavily on the use of save games as an accurate state for testing changes.

Again, thank you all for the input on this subject =)

Share this post


Link to post
Share on other sites
I think that the 'optimal' solution is to design your scripting engine so that it is entirely event driven. Instead of having concurrent threads of script execution where your threads enter a wait state at some arbitrary point, have the scripts respond to events, execute their response to the event entirely and return.

Of course, that means redesigning the entire script engine so it's probably not something you want to think about.

Share this post


Link to post
Share on other sites
Why don't you, instead of all of this nuance, simply not save script state? "You may not save at this time." Decide which scripts may not be interrupted for a save game flag them as necessary. I'm certain many scripts can be restarted after a load with no affect on gameplay whatsoever. Most players probably won't even notice.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by Tergiver
I think that the 'optimal' solution is to design your scripting engine so that it is entirely event driven. Instead of having concurrent threads of script execution where your threads enter a wait state at some arbitrary point, have the scripts respond to events, execute their response to the event entirely and return.

Of course, that means redesigning the entire script engine so it's probably not something you want to think about.


It entirely driven, there is no multi-threading.

The issue arises when you do somthing like this, which is very common:

DoSomeFunction();
Wait(1000);
DoSomeOtherFunction();

In this case, when wait is called it stops the execution of the script. The script continues execution when the engine tells the script that the time has elapsed. This requires the script to know where it left off so that it can continue, and thus if you save during this period you need to know where to go back to when you load.

Quote:
Original post by smr
Why don't you, instead of all of this nuance, simply not save script state? "You may not save at this time." Decide which scripts may not be interrupted for a save game flag them as necessary. I'm certain many scripts can be restarted after a load with no affect on gameplay whatsoever. Most players probably won't even notice.



That was my first instinct, however it is no so easy as that, there are many things that cause the script to wait, (move,wait,action,talk,etc.), the extreme case of this, is potentially a guard script, wherein it goes

while(1)
{
move(waypoint1)
move(waypoint2)
}

this is for guard which patrols two points forever, in this case this script will tecnically never finish.

Share this post


Link to post
Share on other sites
I just wanted to say that saving anywhere can be dangerous. For example, in DeusEx I often saved only to find myself in an impossible situation and had to revert back to non-quick save slot which was couple of maps ago. While taking the other extreme view ie. GTA3 where you can't save during the mission causes me to replay it many times until I get it right. That too is annoying. So perhaps some happy medium between these two extremes would be nice to have. Say, frequent save spots that upon reloading saved game would start up all the actions again from start of script rather from the middle. Not sure if this would work ok as I haven't thought about it fully, just some ideas and personal experiences.

Share this post


Link to post
Share on other sites
I believe there's no way to update old-saved games to work with the new scripts, since this new script could be completely different, for example:
the old script was a 200-line length and the new one has only 5 ! its simply not possible, imho i think you should invalidate all saved games (but only those which are involved with the changed scripts) =(

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
Quote:
Original post by Tergiver
I think that the 'optimal' solution is to design your scripting engine so that it is entirely event driven. Instead of having concurrent threads of script execution where your threads enter a wait state at some arbitrary point, have the scripts respond to events, execute their response to the event entirely and return.

Of course, that means redesigning the entire script engine so it's probably not something you want to think about.


It entirely driven, there is no multi-threading.

The issue arises when you do somthing like this, which is very common:

DoSomeFunction();
Wait(1000);
DoSomeOtherFunction();


If it was entirely event driven, the script would not call 'Wait(1000)' then do something. It would call 'StartTimer("timername", 1000)' and return immediately. When the timer expired, it would fire a timer expired event allowing the script to 'continue' from that point.

In this way, the entry points for your scripts are always the same (some event handler). You'd just save the state of the timer so that when the game state was restored, that timer would fire as if the program had never stopped/reloaded.

But again, this requires re-thinking the way your scripts work entirely.

Share this post


Link to post
Share on other sites
Yes, that would work, but in that case you are almost no better than doing things in 'real' code.

the main issue comes from the fact that your game logic is now very fragmented

e.g.


OnMouseClick()
{
$state=10;
Talk("blah.");
}

OnTalkDone()
{
switch($state)
{
case 10:
$state=20;
Talk("blah2");
break;
case 20:
$state=30
Move(100,100);
break;
}
}

OnMoveDone()
{
if($talkstate==30)
{
Talk("this is annoying...");
}
}


As opposed to the much simpler,this...

OnMouseClick()
{
Talk("blah.");
Talk("blah2");
Move(100,100);
Talk("this is annoying...");
}


Using the 'pure event driven' strategy would work, but it would make it hard to write good scripts.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement