Archived

This topic is now archived and is closed to further replies.

Laroche

Interesting timing problem

Recommended Posts

My scripting engine works off of CCommand objects, which each have a function pointer to the associated function. Each iteration of the game, Script->Execute() is called, which in turn runs through its vector of CCommand objects, and calls pCommand->Execute(), which in turn calls the function through its function pointer. All of this works fine when using just 1 command at a time. However, the real problem starts when I have multiple commands in the vector pointing to the same function. I use time-based movement for everything, including, for example, the amount of time it should take for a DrawText command to "fade-out", that is, reduce from alpha 255 to 0, in X amount of seconds. Here is the part of the print function which is relevent:
    
			        static float FadeAmount = 255.0f;
				static float Integrate = FadeAmount / EffectLimit->Int;

				FadeAmount -= (Integrate / Fps);

				if (FadeAmount < 1)
					FadeAmount = 0;
				
				ManagerPtr TextureMan = pGame->RequestManager("TEXTURE");
				CTexture *pTexture = static_cast<CTexture*>(TextureMan->RequestAsset(2));

				ManagerPtr Man = pGame->RequestManager(1);
				CVertexBuffer * pVb = static_cast<CVertexBuffer*>(Man->RequestAsset(0));

				pVb->GetDevice()->SetTexture(0, pTexture->GetTexture());

				pGame->GetFont()->Draw(pText->Text, "Blocky", XInt->Int, YInt->Int, FadeAmount);
				return 0;
    
This works fine, but when multiple prints are being used with different delays, they all fade the exact same way as the first print command, due to the static variables. I hope im clear enough; It's sort of hard to explain. I would REALLY love to know a way to reduce the alpha without using any static variables at all. If anyone knows how to, I would GREATLY appreciate it; I've been knocking my head against a wall for quite a bit on this problem, and I can't seem to figure out how to get it right. Thanks! EDIT: Just to mention, the above function (well that part of it) gets called every frame. [edited by - laroche on March 16, 2003 2:19:31 PM]

Share this post


Link to post
Share on other sites
It sounds like you are just oh so close, but not quite there. The function pointers make me question exactly what you are doing. Reading the description I would assume that CCommand is an abstract base class and Execute is a virtual function, but then what is the function pointer for? So I suppect there is simple CCommand, no virtual functions and nothing derived off of it.

So my suggestion is create an abstract base class for CCommand that simply defines the interface. Derive each specific type of command off of that class and actually implement Execute within the derived class. You still have a list of CCommand because each command is a CCommand. You call Execute as you do now, but which function is executed is determined by the which derived class the pointer is actually to. The derived classes add whatever variables they specifically need for execution. Your allocations will change. You have to actually create the correct command instead of a generic CCommand object. You are already more or less doing that to set the function pointer. Your destructor also has to be virtual so that you can do a delete using a pointer to CCommand and actually call the right destructor.

If I''m wrong about what you are doing then you need to explain what the function pointer is for. There are some legitimate applications where a polymorphic base class really can''t do what is needed. It seems here it can. If it isn''t then understanding the requirements that preclude it would help.

Share this post


Link to post
Share on other sites
Hey, thanks for the advice, I refactored the design and it works perfectly now. Unfortunately when I want to add more commands I need to update 2 lists instead of 1, but at least everything works properly now. If I end up reducing it to 1 list ill post the results. Thanks again!

Share this post


Link to post
Share on other sites
In order to make the script as general as possible, I use a sort of action template, like what is described in Jim Adams RPG book. The template is a text file which looks like this:

Print,7,Text,Int,Int,Int,Int,Int,Int,0;
End,0,1;
DisplaySprite,5,Text,Int,Int,Int,Int,2;
PlaySong,1,Text,3;
StopSong,1,Text,4;
RemoveSprite,2,Text,Int,5;
~

So the script first parses the file and creates different "Instructions" (these are really just CCommand objects). Each instruction has a vector of CEntry objects, which hold information about each entry. For example, in the case of PlaySong, it is a CCommand Object with a std::vector containing 1 CEntry, which stores its type as TEXT. The script takes these command and shoves them into a list called InstructionList. So when the template is loaded, the Script object has a vector of all possible commands.

Later, when Script->Load(...) is called, goes through a text file, which might look like this:

ClearAssets~
LoadAssets~Assets\\Initial.txt~
PlaySong~Intro~
CreateSprite~Logo~Logo~0~0~0~0~
DisplaySprite~Logo~0~0~4~3~
End~


When the parser checks the first word on a new line, it compares it against the names of all the different commands in the instruction list, then returns a class derived from CCommand with the same information as the one in the instruction list. This is then put into a vector called the CommandList, which is what the script goes through when it executes.

(NOTE that it does not return the object itself, as then if you had multiple print commands for example, it would end up they all had the same entries.)

Heres what this looks like:

  
void CScript::Load(std::ifstream &File)
{
using namespace std;
assert(File.good());
string Buffer;
bool Continue = TRUE;
int j = 0;

CCommand * pComm = 0;

CommandVector.clear();

while (Continue)
{
Buffer = GetNextWord(File,''~'');

pComm = Interpret(Buffer);

for (int i = 0; i < pComm->GetArgAmount(); ++i)
{
CEntry * Entry = 0;
Entry = new CEntry;
Entry->SetID(i);
Buffer = GetNextWord(File,''~'');
switch (pComm->GetEntry(i)->GetEntryType())
{
case ID_NONE:
break;
case ID_TEXT:
{
Entry->SetEntryType(ID_TEXT);
Entry->Text = Buffer;
pComm->SetEntry(i,Entry);
break;
}
case ID_INT:
{
int Int = atoi(Buffer.c_str());
Entry->Int = Int;
Entry->SetEntryType(ID_INT);
pComm->SetEntry(i,Entry);
break;
}
default:break;
}
}

SkipNewLine(File);

assert(pComm != 0);
if (pComm->GetName().compare("End") == 0)
{
Continue = FALSE;
}
pComm->SetOrder(j);
CommandVector.push_back(pComm);
++j;
}
}


The 2 places which have a big switch statement that I have to update are: 1) When loading the template, when it sees the command "Print", it creates a new Print object, and so on. This is fine, as obviously somewhere you ned application specific coding to bind the text to objects. There are probably other ways to bind them directly, but I don''t know how to do them

2) pComm = Interpret(Buffer). The interpret instruction has to go through another switch statement to compare names again and to return a specific object. The problem is that before I would just return a general CCommand object, and now, since every derived class is, in essence, a function (i guess a functor, but i could be wrong), I have to specify what derived class I want to return.

Hope everything makes sense

Share this post


Link to post
Share on other sites
It may help to understand how virtual functions are implemented. A class with virtual functions is a polymorphic class. A polymorphic class has what is called a vtable. The vtable is a list of function pointers. There is one instance of the vtable for each polymorphic class. Each instance of the class has a data member pointing to the vtable for that class.

Say a base class has three virtual functions. So it has a vtable with three entries. A class derived off of the base has equivalent pointers in those same positions. If the derived class doesn''t implement the function then that pointer points to the function in the base class. If it does implement it then that pointer points to it''s implementation. An abstract base class has a pure virtual function and the pointer for that function in the base class is null. Obviously if you call that you will crash. So you are not allowed to actually create an instance of the base class nor a derived class that doesn''t implement that function. CCommand should be an abstract base class with Execute a pure virutal function. You can''t execute a CCommand, only a class derived off of CCommand that implements Execute.

Now how it works is that you have a list of pointers to CCommand. That is the definition of the pointer, but the pointers actually point to whatever types of commands. When you call base::Execute it is going to go to the vtable, get the pointer at the offest for Execute and call it. If it was actually a CCommand that would be null and you would crash. It isn''t though. It is actually pointing at an instance of Print that implements Execute. So the pointer is actually pointing at Print::Execute. Say the next command is PlaySong so the pointer at the same offset is PlaySong::Execute.

One more thing may help. That is the data storage. The data storage for inheritance heirachies is exactly the same as nested structures. The first thing for a polymorphic class is the vtable pointer and there is only one, excluding multiple inheritance, not matter how deep the heirarchy. That just to be technically correct. Then you have a structure for the data members of the derived class and the first thing in that structure is the structure for the base class which may in turn inherit off another class. So a pointer to the derived classes data is a pointer to the base classes data. So a derived class can always be used as though it is an instance of the base class which is why you can use a list of CCommand pointers.

There isn''t really any way around the parser. It can be done generically. I''m not sure there is much benefit to doing so. One more case in a switch statement or one more entry in an array. Small differance. You still have to call the constructor with the only real differance being whether you only call it once to construct the template you copy or each time you create a command. If you copy a template you have to have a does the initialization normally done by the constructor since the constructor and allocation are tied together.

Share this post


Link to post
Share on other sites