Preserve variable state after function call

Started by
14 comments, last by _Sigma 16 years, 10 months ago
So I've come to realize that my design is flawed. I was originally going to have my main loop call a AS function every frame, where the level's main processing would go. See here for more information. Yet, after each call to my AS function main(), the function's variables are not preserved (which makes sense now that I think about it). So, is there a way to solve this problem? That is, for each function call, preserve the stack? The only work around I see is to have a while(1) in the script function, and then have a function, suspend, for example, which tells the kernel to issue a pause command to AS,(issued at the end of the script's while(1)), at which point the engine pauses the script so it can do everything else(rendering for example). I would rather not do this though...
Advertisement
Why don't you use global variables? They will be preserved between function calls. Unless you also recompile the script every time. ;)

Or maybe you need to expose an object from your game engine that will let the script store game state information. Sort of like the Session object in ASP web pages.

You can also have the engine automatically suspend the script after a certain time, by using the line callback and checking if the time is up for the script. You will not have a nice once-per-frame-update this way though, instead the script will pretty much be a second thread in your game.

Your own suggestion is not a bad one either. It will let the script decide when it has finished processing. Perhaps with an added protection from the game engine to time-out the script in case it takes too long to process, to avoid frame freeze.

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Quote:Why don't you use global variables? They will be preserved between function calls. Unless you also recompile the script every time. ;)
Not really an option as there are going to be more than 1 function in the script. So I worry about name collision, as well as globals == bad. lol

Quote:Or maybe you need to expose an object from your game engine that will let the script store game state information. Sort of like the Session object in ASP web pages.
How memory intensive is this? My guess is reasonably...

Quote:Your own suggestion is not a bad one either. It will let the script decide when it has finished processing. Perhaps with an added protection from the game engine to time-out the script in case it takes too long to process, to avoid frame freeze.
Well maybe I'll go with this then. What do you figure is a reasonable timeout?
The reasonable time-out depends on the type of your game. Is it fast-paced, or not? Maybe it's is turn-based?. It also depends on how many scripts you wish to run during each frame, i.e. how much time do you think is reasonable for each script to take without crippling the game, and thus the player experience? Unless your game is turn-based, you should probably not set the time-out higher than 1 second, or the player will think the game crashed or something before your application suspends the script.

If you don't want to use globals, then you could use a script declared class instead. The current function would then be a method of this class and it could store its persistent values in the class. By declaring an interface you can even guarantee that the script class implements the methods you need from the game engine. Thinking about it, this may actually be the easiest approach, though you should probably implement the time-out anyway for good measure.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

heh, sounds like java!

Looks like I need to redesign the way I call functions.

Just so I'm clear:

Get a context from the engine -> Get function ID -> Prepare context -> Execute -> Release

So if a function is suspended, the context is still valid, and if we call release and then attempt to use it, we crash on a Null pointer.

Currently I have only one context available at any given time. When I call a function, I follow the steps above, and then release no matter what. So when I try my idea above, since I release the context, my game crashes (obviously).

my proposal to fix this:

When calling a function, store in an 4-way associative container the:
Function delaration | ID | context | state
This way, when function is called, it can check to see if it already has a valid context within which it can run, and just resume. As well, we can check its state so we know if it has fully executed or not.

Is this a good idea at all? Any serious flaws? Need more information?

Cheers
Chris
Well, as you already figured out, you should only call Release on the context when you're done with it. If you instead wish to reuse the same context, once it is done executing the previous function, it's just a matter of calling Prepare on it again, and then Execute.

You probably don't want to keep too many contexts around, only as many as you have script functions running in parallel. Each context keeps it own stack for the script execution, thus they are quite heavy on the memory consumption (by default, a little over 1 kb each).

I don't think you need a 4 way associative container, that would be a quite complex structure. Do you really need to keep track of that much information?

How many long running scripts are you planning on having around?

Maybe you just need to keep a list of active contexts. In each frame of the game you call Execute on each of these contexts and allow them to run for a short while. The ones that return from Execute with anything else than asEXECUTION_SUSPENDED have terminated and should be removed from the list.

Well, I can't really propose any design for the way you'll use AngelScript unless you tell me exactly what you want to accomplish. I'm just throwing ideas at you, whether they suit your needs or not is up to you to judge.

By the way, have you examined the samples that I've included in the SDK? They may give you some more ideas. Especially the sample for concurrent scripts and the one for co-routines. In them I implement a couple of different variants of script managers.

Some day I'll take the time to expand upon them and make a more generic manager that can be used right out of the box.

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

My design assumes one class per module (source file). You can have more than one class, but only class 'exposed' to the game engine. I also export 1 global function with the only job of returning the name of the class in the module.

In the game engine, when I load the module I call the global function of the module, which returns the name of the class. I then use AngelScript's engine to create an instance of the class by its typeID. From then on all function calls into that module are through the instance of the class and calling its member functions. State variables are stored as member variables of the class.

class TestClass{TestClass(){ m_counter = 0;}void DoFrame(){ m_counter++;}int m_counter;}string GetClassName(){ return "TestClass";}
Why standardize the function that returns the class name, when you could just standardize the class name? Or are script-defined classes available in other modules now? Or are you throwing all your scripts into one module?
Quote:Maybe you just need to keep a list of active contexts. In each frame of the game you call Execute on each of these contexts and allow them to run for a short while. The ones that return from Execute with anything else than asEXECUTION_SUSPENDED have terminated and should be removed from the list.
Yes I think this is what I what...I think this is what I meant to say previous, but it came out wrong? I was in a bit of a rush...Regardless, this seems good.

Quote:I don't think you need a 4 way associative container, that would be a quite complex structure. Do you really need to keep track of that much information?
I guess not eh? I will keep IDs seperate, and then just use a map holding a <string,string> to hold the declaration and the context.

Quote:By the way, have you examined the samples that I've included in the SDK? They may give you some more ideas. Especially the sample for concurrent scripts and the one for co-routines. In them I implement a couple of different variants of script managers.
I have. I suppose I wanted something a little more generic, one that fit a little nicer with my current code base.

Quote:Some day I'll take the time to expand upon them and make a more generic manager that can be used right out of the box.
Bah! That takes the fun out of it!

I think you've pointed me in the right direction. I'll try to impliment this and see what happens! Perhaps I'll post my code for some peer review? O_o
Alright, I have this working. You just call the function whenever you want, and if it has been previously suspended, it just starts it up again.

CallFunction
void TScript::CallFunction( std::string fn ){	IDIterator it;	ContextIterator cIt;	bool hasContext = false;	int r =0;	int funcId = 0;	cIt = m_context.find(fn);	if(cIt == m_context.end())	{		//not present, so create one		m_context.insert(std::pair<std::string,asIScriptContext*>(fn,m_engine->CreateContext()));		//get our context		cIt = m_context.find(fn);	}	else	{		hasContext = true;	}	//Attempt to get the function id from the function id map. If it isn't present ; add it.	//try to get the Id...	it = m_fnMap.find(fn);		if(it == m_fnMap.end())	{		//ID not in map. add it		try	 	{			// Find the function id for the function we want to execute.			 funcId = m_engine->GetFunctionIDByDecl(0, fn.c_str());			if( funcId < 0 )			{				TLog::GetInstance().LogMessage("TScript::CallFunction","The script: " + fn + " does not exist.",MSG_ERROR);				throw TFatalException("The script: " + fn + " does not exist.\n\nIn:\nTScript::CallFunction() ");			}	 		m_fnMap.insert(std::pair<std::string,int>(fn,funcId));	 	}	 	catch (...)	 	{			throw TFatalException("Failed to add registered function to map. Function was: " + fn +" \n In:\n  TScript::ExposeFn() ",T_ERROR);	 	}	}	else	{		//the id was in the map, so we can just grab it.		//we assume that this should never be zero		funcId = it->second;	}	// Prepare the script context with the function we wish to execute. 	// We only need to prepare the context if it doesn't exist.	if(!hasContext)	{		 r = cIt->second->Prepare(funcId);		if( r < 0 ) 		{			TLog::GetInstance().LogMessage("TScript::CallFunction","Failed to prepare script context.",MSG_ERROR);			cIt->second->Release();			throw TFatalException("Failed to prepare script context.\n\nIn:\nTScript::CallFunction() ");		}	}	//call it	r = cIt->second->Execute();	//did we finish?	if(r == asEXECUTION_FINISHED)	{		cIt->second->Release();		m_context.erase(cIt);	}		}

The script
void main()		{		  		  			  Test t;			  int i = 0;			  			  while(1==1)			  {				  if( i < 3)				  {					  t.SetX(100);					  t.SetY(200);					  Log("X is:" + String(t.GetX()));					  Log("Y is:" + String(t.GetY()));					  t.x = 69;					  t.y = 6900;					  Log("X is:" + String(t.GetX()));					  Log("Y is:" + String(t.GetY()));					  				   }				   				   Engine.Pause();			   }		  		}


And
Usage
while(true)	{		if(m_wnd)		{			//deal with windows messages here			if(!m_wnd->HandleMessages())				break;			//////////////// main loop here ////////////////				//call into the script				m_script.CallFunction("void main()");			//force redraw to display FPS			//Very slow!!! Takes about 30 fps off our max fps			InvalidateRect(m_wnd->GetHWND(),NULL,FALSE);			//////////////// end main loop here ////////////////		}		else		{			throw TFatalException("Window not present, cannot run main message loop. In:\n Stack:\n TKernel::MainLoop()", T_ERROR);		}		fps.Tick();	}

This way I can repeatedly call my function and have it auto resume for me. As well, this works for any function.

My only concern is the amount of finds(...) going on. I don't plan on having a lot of concurrent functions though.

Is find O(logn) or O(n)?

I would appreciate any feedback on this.

This topic is closed to new replies.

Advertisement