Tiny bug in 0.60b1

Tiny bug in 0.60b1

Discuss the source code and development of Spring Engine in general from a technical point of view. Patches go here too.

Moderator: Moderators

Post Reply
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Tiny bug in 0.60b1

Post by deadram »

How did I find this bug... Dumb luck :D

Anyways, here's the problem... in GlobalAITestScript.cpp, the line

Code: Select all

static CGlobalAITestScript ts;
is run before the line in RegHandler

Code: Select all

RegHandler regHandler("Software\\SJ\\spring");
But in the creation of CGlobalAITestScript, there is at least one call to regHandler.GetString(...).

If you follow the creation of the CGlobalAITestScript object, "ts", in Script.cpp CScript::CScript, CScriptHandler::Instance()->AddScript(name,this); is called. In CScriptHandler::Instance(), a CScriptHandler; object is created. that creates a CglList. Finally, in CglList::CglList, a call to regHandler.GetString is made.

Why doesn't this generate a fatal error? Because RegQueryValueEx fails, and returns the 'default' value. Even if the value has been set, regkey doesn't exist yet. This error could get worse if a RegHandler::Set[String/Int]() is called.

Code: Select all

#include <string>
#include <windows.h>
#include <winreg.h>

using std::string;

string GetString(HKEY regkey, string name, string def)
{
	unsigned char regbuf[100];
	DWORD regLength=100;
	DWORD regType=REG_SZ;

	if(RegQueryValueEx(regkey,name.c_str(),0,&regType,regbuf,&regLength)==ERROR_SUCCESS)
		return string((char*)regbuf);

	return def;
}

int main()
{
	printf(GetString(NULL, "whatever", "The RegQuery fails and returns this string"));
}
I've already managed to make regHandler GCC/linux compatible, but from this error (and the possibility of it's reoccurance), I'll try to get the original reghandler recoded using static functions and variables, with a bool check to see if the reg key is opened. That ~should~ prevent this type of thing from happening.
SJ
Posts: 618
Joined: 13 Aug 2004, 17:13

Post by SJ »

Probably better that you rewrite it as a singleton while you are at it.
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Post by deadram »

singleton? I'll have to google that one... XD

Here's a link to the source:
http://members.fortunecity.com/2069/

Sorry, no hosting big files, I have the binary built and tested, but I can't give it out :P

Just wait for SJ to post an updated build, and no worries, this is a tiny tiny bug :D

BTW, SJ, what do you think about a template, for the Get/SetInt... REG_BINARY is really nice about template stuff. Could force a type by using "const T& rNumValue" in the params, instead of returning it. This would prevent people from loading/storing signed ints into a function that only handles unsigned, or 64-bit numbers into a 32-bit value :D

----------------- Edit ----------------------------

Hummm... singleton looks like fun ^.~ I'll see what I can do... I still have to recode reghandler anyways to make it OS independant, using an optional file to store settings in windows, and a forced file in *nix.

While I'm at it... are there any plans to thread spring.exe? If so, do yous have a Mutex/Locker coded? need me to make one? :D
SJ
Posts: 618
Joined: 13 Aug 2004, 17:13

Post by SJ »

Spring is already threaded, although only if you run as the server (the server part run in its own thread) we are using windows standard mutexes for syncronization.

The reason for those datatypes in reghandler was that those are what the windows registry store nativly if I dont remember all wrong. But sure make it templated if you implement your own way to store stuff.
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Post by deadram »

I'll get the thread safe singleton started later today, need to have a nap first :D

It will work the same as reghandler does, and there shouldn't be a need to change any code... If there is any changes, something like this will take care of it:

Code: Select all

#define regHandler (RegHandler::GetInstance())
GetInt(string, unsigned int) will work, but will just be an inline call to GetNum(string, T<unsigned int>).

At least that's how I see it now... should be finished by Sunday, time permitting
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Post by deadram »

I've been thinking about this bug for a while... And my guess is it's going to crop up again and again in other codeblocks unless a new "creation" system, erm... format? meh... anal retentivness... is used. I was thinking of going over every object, and making the constructor set values to NULL, 0, etc... Add in a "static <type>* GetInstance(void)", or "static void GetInstance(<type>&)" to all the objects. By doing this, at startup, any global objects can be "init" in winmain which forces a "creation priority". So the program starts up, runs through each global object's constructor (which set the memeber values to 0, NULL, etc...) then winmain is encountered. Each global object's "GetInstance" function is called. Inside the GetInstance, a singleton is implemented, or not, depending on the object. After that a call from GetInstance to the objects "init()" is made. Init will do things like calling other global objects, creating memory for strings, or BYTE streams, etc... This allows us (the coders) to say what object comes into existance first, and what comes into existance last. It also gives us a better opertunity to say what object is "deleted" first, and what is "deleted" last. Here's a sort of template of what that would entail...

Code: Select all

class CTemplate // of a regular object; ie: not a singleton
{
public:
    static CTemplate GetInstance(/*Params for constructor*/
                                 UINT uiValue, BYTE* pByteData)
    {
        // Create the object
        CTemplate NewTemplate();

        // Init the member variables, and do init work
        NewTemplate.Init(/*Params for constructor*/uiValue, pByteData);

        return NewTemplate;
    }

/* public member functions, and variables go here */

private:
    CTemplate(/*No params for the new constructors*/)
        :    m_uiValue    (0),
             m_pByte       (NULL)
    {}

    void Init(/*Params for constructor*/ UINT uiValue, BYTE* pByteData)
    {
        m_uiValue = uiValue;
        m_pByte = pByteData;
        // Or get size of byte data, and create a
        // copy using "new BYTE[]" or malloc

        // Make calls to other (global?) objects all the way down here!
    }
};
Could also look up some pragmas to "poison" or at least "warn" about using an objects creator, and only disable that poisoning durring that objects GetInstance; if we wanted public constructors, that is. I know it's alot of code to change, and it's alot of force for such a small problem, but this could make the game that much more stable. First off, because the startup is cleaner. Secondly, because all the objects being created durring the game are forced into cleanliness (ie: bad memory allocation, deallocation will be easier to find). Thirdly, (if I remeber correctly; because of larger offsets to the function tables) alot of code in an objects constructor has a tendancy to slow down the thread (sure, by a few 'ticks', but still... we're being anal, remeber?). We could even add in something like this to each public member function:

Code: Select all

#ifdef DEBUG
if (! memeber_debug_bConstructorRun)
  MessageBox(NULL, "Trying to use CTemplate::DoSomething() but object CTemplate does not exists", "Object Usage Error", MB_OK | MB_ICONERROR);

if (! memeber_debug_bInitRun)
  MessageBox(NULL, "Trying to use CTemplate::DoSomething() but object CTemplate was not init", "Object Usage Error", MB_OK | MB_ICONERROR);
#endif
Hope one of you lead devs can get back to me soon :D
User avatar
AF
AI Developer
Posts: 20687
Joined: 14 Sep 2004, 11:32

Post by AF »

I'm glad that soemthigns been put up to stabilize GlobalAI further than it already is. I'm sidetracking to spend time making ym AI spend elss time processing rather than implementing more features, and my OTA AI def parser crashes if the file is too long, and that means the majority fo AI defs from OTA are too long already. My mex algorithm and building placement algorithm crash the engine too, they just take too much processing and crash the engine as a result.
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Post by deadram »

Well i'm not going to waste time on this unless one of the lead devs gets back to me and gives the "A'OK". It's to much work to fix the intermingled spagetti code that spring has become... We might as well be using goto's with the way the code is tossed about :P (And I'm not talking syntax, I can use "indent" to fix that). The thing is, alot of code will have to be moved, to clean up the start-up environment. Once that's done, someone will have to trace the trail an object's init makes, and bring into existance all the required global objects beforehand. Then, you have to iterate over every line of code to find out when non-global objects come into existance and tweak them up. I'll do it, but not without the go ahead... I already coded a friken settings program with tool tips and tabs, with a "first run" and "new features" wizard mode. Anyways, the whole thing, mostly coming up with the tool tips, took over a week, and it has yet to be put into the cvs... along with the "static reghandler" fix I already posted to the website listed in this thread...

I understand they can't take every hairbrained idea and include it into the project, but I'm not about to code for a week to find out it's not being used...

On that note, could a lead dev give the ~go ahead~ to me, or the ~don't go ahead~.
SJ
Posts: 618
Joined: 13 Aug 2004, 17:13

Post by SJ »

If you do them as singletons you dont have to worry about when to initialize them since they are initialized at first use. The problem is to deallocate them which is the main advantage of having them as global objects (you will have to hook atexit or something to do it nicely).

Your modified reghandler felt a bit more messy than the old one so I just rewrote that as a singleton instead.
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Post by deadram »

You might consider trying this get/set method. It must be declaired and defined in the header, a'cause it's a template. Main advantage? You can store a struct/enum, 1024-bit int etc... into the registry. Main disadvantage is that the code storing and restoring the value has to be carful about the type of variable. Default values ~must~ be casted to the appropriate type, otherwise you'll end up storing a 16-bit int array into a struct, or the likes. Alot of the code is based off my static version of the reghandle, but it should be easy enough to delete the unimportant stuff. This sort of stuff:

Code: Select all

CREATE_ERROR_SHR(RegHandlerErrors, ERROR_NOT_ENOUGH_MEMORY);
is used to create RegHandlerErrors objects. It's from a program I've started to make that uses try catch error handling. It can be turned into something to return an HRESULT, or just removed completely. Either way, enjoy :P

---- Edit ----
I've changed GetValue from using the pointer to just passing the BYTE data over the stack. This way code doesn't have to worry about doing a "delete [] (BYTE*) pRetValueFromGetValue;"

Code: Select all

class RegHandler
{
// Blah blah blah
// ...

public:
	template <typename T> static T		GetValue(const TCHAR* ptcName, const T& rDefaultValue);
	template <typename T> static void	SetValue(const TCHAR* ptcName, const T& rNewValue);
};


template <typename T> /*static*/ T* RegHandler::GetValue(const TCHAR* ptcName, const T& rDefaultValue)
{
	HRESULT hrErrorCode;
	HKEY hKeySettings;

	// Open the reg key
	hrErrorCode = OpenKey(hKeySettings);
	if (hrErrorCode != ERROR_SUCCESS)
	{
		RegHandlerErrors* e = CREATE_ERROR_SHR(RegHandlerErrors, hrErrorCode);
		e->DisplayMsg();
		e->Release();
		return rDefaultValue;
	}

	DWORD	dwRegLength = 0;
	DWORD	dwRegType;

	// Find out byte size of value, and type
	hrErrorCode = RegQueryValueEx(hKeySettings, ptcName, 0, &dwRegType, NULL, &dwRegLength);
	if (hrErrorCode != ERROR_SUCCESS) // This returns ERROR_SUCCESS, unless bad permissions, Key DNE, etc... happend
	{
		hrErrorCode = CloseKey(hKeySettings);
		if (hrErrorCode != ERROR_SUCCESS)
		{
			RegHandlerErrors* e = CREATE_ERROR_SHR(RegHandlerErrors, hrErrorCode);
			e->DisplayMsg();
			e->Release();
		}

		return rDefaultValue;
	}

	// Make ~sure~ this is a binary data type
	if (dwRegType != REG_BINARY)
	{
		RegHandlerErrors* e = CREATE_ERROR_SE(RegHandlerErrors, IDS_E_REG_HANDLER_INVALID_DATA_TYPE, FALSE);
		e->DisplayMsg();
		e->Release();

		return rDefaultValue;
	}

	// Create the byte data...
	static BYTE* pbyteRegBuf = NULL;
        if (pbyteRegBuf)
          delete [] pbyteRegBuf;

        pbyteRegBuf = new [dwRegLength];
	if (! pbyteRegBuf)
		throw CREATE_ERROR_SHR(RegHandlerErrors, ERROR_NOT_ENOUGH_MEMORY);

	hrErrorCode = RegQueryValueEx(hKeySettings, ptcName, 0, NULL, (LPBYTE)pbyteRegBuf, &dwRegLength);
	if (hrErrorCode != ERROR_SUCCESS)
	{
		hrErrorCode = CloseKey(hKeySettings);
		if (hrErrorCode != ERROR_SUCCESS)
		{
			RegHandlerErrors* e = CREATE_ERROR_SHR(RegHandlerErrors, hrErrorCode);
			e->DisplayMsg();
			e->Release();
		}

		throw CREATE_ERROR_SHR(RegHandlerErrors, hrErrorCode);
	}

	/* Now we have pbyteRegBuf filled with the data */

	hrErrorCode = CloseKey(hKeySettings);
	if (hrErrorCode != ERROR_SUCCESS)
	{
		RegHandlerErrors* e = CREATE_ERROR_SHR(RegHandlerErrors, hrErrorCode);
		e->DisplayMsg();
		e->Release();
	}

	// Caller must remeber to "delete [] (BYTE*)pbyteRegBuf;"
	return (T)(*pbyteRegBuf);
}

template <typename T> /*static*/ void RegHandler::SetValue(const TCHAR* ptcName, const T& rNewValue)
{
	HRESULT hrErrorCode;
	HKEY hKeySettings;

	// Open the reg key
	hrErrorCode = OpenKey(hKeySettings);
	if (hrErrorCode != ERROR_SUCCESS)
	{
		RegHandlerErrors* e = RegHandlerErrors::Create(hrErrorCode, __FILE__, __LINE__);
		e->DisplayMsg();
		e->Release();
		return rDefaultValue;
	}

	// Store the value there in binary
	hrErrorCode = RegSetValueEx(hKeySettings, ptcName, 0, REG_BINARY, (BYTE*)&rNewValue, sizeof(rNewValue));
	if (hrErrorCode != ERROR_SUCCESS)
	{
		HRESULT hrErrorCode2 = CloseKey(hKeySettings);
		if (hrErrorCode2 != ERROR_SUCCESS)
		{
			RegHandlerErrors* e = RegHandlerErrors::Create(hrErrorCode2, __FILE__, __LINE__);
			e->DisplayMsg();
			e->Release();
		}

		THROWSE(RegHandlerErrors, hrErrorCode);
	}

	/* Now we have pbyteRegBuf filled with the data */

	hrErrorCode = CloseKey(hKeySettings);
	if (hrErrorCode != ERROR_SUCCESS)
	{
		RegHandlerErrors* e = RegHandlerErrors::Create(hrErrorCode, __FILE__, __LINE__);
		e->DisplayMsg();
		e->Release();
	}
}
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Post by deadram »

SJ (or other people working on CGlobalAI... erm... I meen global objects... *hint hint*), you might want to read this, on singletons. The fact that TA isn't threaded disappoints me, however, the server is threaded, so keeping that in mind, here's a short, but interesting article on singletons, and global objects in a threaded environment.

http://www.devarticles.com/c/a/Cplusplu ... hreadSafe/
User avatar
jcnossen
Former Engine Dev
Posts: 2440
Joined: 05 Jun 2005, 19:13

Post by jcnossen »

The fact that TA isn't threaded disappoints me, however, the server is threaded, so keeping that in mind, here's a short, but interesting article on singletons, and global objects in a threaded environment.
You should keep in mind the problems involved in breaking up the application in multiple threads. One extra thread for drawing for example will require copying the game state to a temporary location for drawing. It just adds to complexity and doesn't really make the game any better IMO.

If you are using threads I would say just don't use singletons. Just initialize something when all the other stuff is initialized and not when you actually need it. Then you can make sure it's thread-safe. Why make it more complex than it is?
IMSabbel
Posts: 747
Joined: 30 Jul 2005, 13:29

Post by IMSabbel »

About threads: Are the AIs (when they will have arrived) running multithreaded?
User avatar
deadram
Posts: 37
Joined: 25 Jul 2005, 15:55

Post by deadram »

IMSabbel wrote:About threads: Are the AIs (when they will have arrived) running multithreaded?
Nothing is threaded. Unless your running as a host, in which case the server is threaded.
Zaphod wrote: You should keep in mind the problems involved in breaking up the application in multiple threads. One extra thread for drawing for example will require copying the game state to a temporary location for drawing. It just adds to complexity and doesn't really make the game any better IMO.

If you are using threads I would say just don't use singletons. Just initialize something when all the other stuff is initialized and not when you actually need it. Then you can make sure it's thread-safe. Why make it more complex than it is?
Your right, unless you implement threading properly from the start, you end up introducing more bugs... I was only suggesting making the regHandler thread safe because the server thread could potentially be requesting data from reghandler while the mean thread was doing the same. Creating two "singleton" regHandler objects. The simple fact is, there is no control durring TA Spring's startup/shutdown by the coder, so if you wanted to make it stable, you'd have to recode half the dam project. I did say I'd prefer if TA was threaded, but only to some extent. If you have more threads then (Virtual? in the case of HTT) CPUs, you actually slow down processing. The proper way to thread is to get a count of (V)CPUs and create a thread for each. Then by passing off jobs to the first free thread (using windows Mutexes and WaitForMultipleObjects) you optimize the usage of the CPU. If you don't code for the threaded model you might as well be wasting your CPU time on spanking your memory XD PlayStation 3 is rumored to have a multi core CPU and P4's already support HTT. It's only a matter of time before a single physical CPU, has 4, or 8, or more Virtual CPUs. Imagine only using 1/8th of the CPU to run a program that's already low on resources.

But yah, I doubt that TA will be threaded without tossing half the code ;p or introducing 500-billion more bugs :P
SJ
Posts: 618
Joined: 13 Aug 2004, 17:13

Post by SJ »

The server thread isnt created until long after reghandler have been used the first time so there is no risk of error there.

And it would take a huge amount of work to get spring to work in a multithreaded way since apart from the usual syncronizing problem Spring must also run in perfect syncronicity between the different machines which make thread handling even more difficult. There would be nothing hindering an ai to create some extra threads internally to do some background processing as long as it only calls spring from the main thread.
IMSabbel
Posts: 747
Joined: 30 Jul 2005, 13:29

Post by IMSabbel »

Ah, maybe i misunderstood the whole AI-interface thing ( i thought from a quick browse of the forum that the AI is running as a seperate process, being offered an interface to get data and polled every frame for outstanding commands)
SJ
Posts: 618
Joined: 13 Aug 2004, 17:13

Post by SJ »

You can view the AI that way if you want IMS since that interface should be the only way it communicates with spring. But it runs in the same process as the rest of spring.
Post Reply

Return to “Engine”