(svn r9201) [NoAI] -Change: make adding a default-constructor for DefSQClass optional noai
authortruelight
Thu, 15 Mar 2007 13:36:45 +0000
branchnoai
changeset 9404 ef9e171617a3
parent 9403 0a2377009af4
child 9405 df6f3b4b0038
(svn r9201) [NoAI] -Change: make adding a default-constructor for DefSQClass optional
-Add: added AIFactory code for squirrel. Now scripts can make theirself
known to the core in a simular way as in C++. Just make sure to add
your AIFactory instance to the GLOBAL space!
-Add: even more function in SquirrelEngine
bin/ai/SQNoAI/main.nut
src/ai/core/ai_base.hpp
src/ai/core/ai_company.hpp
src/ai/core/ai_controller.hpp
src/ai/core/ai_map.hpp
src/ai/core/ai_town.hpp
src/ai/squirrel/engine.cpp
src/ai/squirrel/engine.hpp
src/ai/squirrel/squirrel.cpp
src/ai/squirrel/squirrel.hpp
src/ai/squirrel/squirrel_class.hpp
--- a/bin/ai/SQNoAI/main.nut	Thu Mar 15 13:28:11 2007 +0000
+++ b/bin/ai/SQNoAI/main.nut	Thu Mar 15 13:36:45 2007 +0000
@@ -1,9 +1,16 @@
 /* Extend our own AI from AIController so we can access basic things. */
 class SQNoAI extends AIController {
-	base = AIBase();
-	company = AICompany();
-	map = AIMap();
-	town = AITown();
+	base    = null;
+	company = null;
+	map     = null;
+	town    = null;
+
+	constructor() {
+		this.base = AIBase();
+		this.company = AICompany();
+		this.map = AIMap();
+		this.town = AITown();
+	}
 
 	function GameLoop();
 }
@@ -42,5 +49,14 @@
 	}
 }
 
-/* Tell OpenTTD we have an AI */
-RegisterAI("SQNoAI");
+class FSQNoAI extends AIFactory {
+	function GetAuthor()      { return "OpenTTD Dev Team"; }
+	function GetName()        { return "SQNoAI"; }
+	function GetDescription() { return "Rather simple AI that tests all the functions of the AI layer in Squirrel."; }
+	function GetVersion()     { return 1; }
+	function GetDate()        { return "2007-03-14"; }
+	function CreateInstance() { return "SQNoAI"; }
+}
+
+/* Tell the core we are an AI */
+iFSQNoAI <-FSQNoAI();
--- a/src/ai/core/ai_base.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/core/ai_base.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -43,6 +43,7 @@
 void SQAIBaseRegister(SquirrelEngine *engine) {
 	DefSQClass <AIBase> SQAIBase("AIBase");
 	SQAIBase.PreRegister(engine);
+	SQAIBase.AddConstructor(engine);
 	SQAIBase.DefSQFunction(engine, &AIBase::Random,      "Random");
 	SQAIBase.DefSQFunction(engine, &AIBase::RandomRange, "RandomRange");
 	SQAIBase.DefSQFunction(engine, &AIBase::Chance,      "Chance");
--- a/src/ai/core/ai_company.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/core/ai_company.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -75,6 +75,7 @@
 void SQAICompanyRegister(SquirrelEngine *engine) {
 	DefSQClass <AICompany> SQAICompany("AICompany");
 	SQAICompany.PreRegister(engine);
+	SQAICompany.AddConstructor(engine);
 	SQAICompany.DefSQFunction(engine, &AICompany::SetCompanyName,   "SetCompanyName");
 	SQAICompany.DefSQFunction(engine, &AICompany::GetCompanyName,   "GetCompanyName");
 	SQAICompany.DefSQFunction(engine, &AICompany::GetCompanyValue,  "GetCompanyValue");
--- a/src/ai/core/ai_controller.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/core/ai_controller.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -35,3 +35,12 @@
 };
 
 #endif /* AI_CONTROLLER_HPP */
+
+#ifdef SQUIRREL_CLASS
+void SQAIControllerRegister(SquirrelEngine *engine) {
+	DefSQClass <AIControllerSquirrel> SQAIController("AIController");
+	SQAIController.PreRegister(engine);
+	SQAIController.DefSQFunction(engine, &AIControllerSquirrel::GetTick, "GetTick");
+	SQAIController.PostRegister(engine);
+}
+#endif /* SQUIRREL_CLASS */
--- a/src/ai/core/ai_map.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/core/ai_map.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -60,6 +60,7 @@
 void SQAIMapRegister(SquirrelEngine *engine) {
 	DefSQClass <AIMap> SQAIMap("AIMap");
 	SQAIMap.PreRegister(engine);
+	SQAIMap.AddConstructor(engine);
 	SQAIMap.DefSQFunction(engine, &AIMap::IsValidTile, "IsValidTile");
 	SQAIMap.DefSQFunction(engine, &AIMap::GetMapSize,  "GetMapSize");
 	SQAIMap.DefSQFunction(engine, &AIMap::GetMapSizeX, "GetMapSizeX");
--- a/src/ai/core/ai_town.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/core/ai_town.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -62,6 +62,7 @@
 void SQAITownRegister(SquirrelEngine *engine) {
 	DefSQClass <AITown> SQAITown("AITown");
 	SQAITown.PreRegister(engine);
+	SQAITown.AddConstructor(engine);
 	SQAITown.DefSQFunction(engine, &AITown::GetMaxTownID,  "GetMaxTownID");
 	SQAITown.DefSQFunction(engine, &AITown::GetTownCount,  "GetTownCount");
 	SQAITown.DefSQFunction(engine, &AITown::IsValidTown,   "IsValidTown");
--- a/src/ai/squirrel/engine.cpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/squirrel/engine.cpp	Thu Mar 15 13:36:45 2007 +0000
@@ -94,7 +94,22 @@
 	sq_createslot(vm, -3);
 }
 
-void SquirrelEngine::CallMethod(HSQOBJECT instance, const char *method_name)
+bool SquirrelEngine::MethodExists(HSQOBJECT instance, const char *method_name)
+{
+	int top = sq_gettop(this->vm);
+	/* Go to the instance-root */
+	sq_pushobject(this->vm, instance);
+	/* Find the function-name inside the script */
+	sq_pushstring(this->vm, method_name, -1);
+	if (SQ_FAILED(sq_get(this->vm, -2))) {
+		sq_settop(this->vm, top);
+		return false;
+	}
+	sq_settop(this->vm, top);
+	return true;
+}
+
+void SquirrelEngine::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret)
 {
 	/* Store the current top */
 	int top = sq_gettop(this->vm);
@@ -109,7 +124,8 @@
 	}
 	/* Call the method */
 	sq_pushobject(this->vm, instance);
-	sq_call(this->vm, 1, 0, 0);
+	sq_call(this->vm, 1, ret == NULL ? SQFalse : SQTrue, SQFalse);
+	if (ret != NULL) sq_getstackobj(vm, -1, ret);
 	/* Reset the top */
 	sq_settop(this->vm, top);
 }
--- a/src/ai/squirrel/engine.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/squirrel/engine.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -62,9 +62,17 @@
 	void AddClassEnd();
 
 	/**
-	 * Call a method of an instance.
+	 * Call a method of an instance, in various flavors.
 	 */
-	void CallMethod(HSQOBJECT instance, const char *method_name);
+	void CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret);
+	void CallMethod(HSQOBJECT instance, const char *method_name) { this->CallMethod(instance, method_name, NULL); }
+	const char *CallStringMethod(HSQOBJECT instance, const char *method_name) { HSQOBJECT ret; this->CallMethod(instance, method_name, &ret); return ObjectToString(&ret); }
+	int CallIntegerMethod(HSQOBJECT instance, const char *method_name) { HSQOBJECT ret; this->CallMethod(instance, method_name, &ret); return ObjectToInteger(&ret); }
+
+	/**
+	 * Check if a method exists in an instance.
+	 */
+	bool MethodExists(HSQOBJECT instance, const char *method_name);
 
 	/**
 	 * Creates a class instance.
@@ -80,12 +88,40 @@
 	 * @note This will only work just after a function-call from within Squirrel
 	 *  to your C++ function.
 	 */
-	static bool GetInstance(HSQUIRRELVM vm, SQUserPointer *ptr) { return SQ_SUCCEEDED(sq_getinstanceup(vm, 1, ptr, 0)); }
+	static bool GetRealInstance(HSQUIRRELVM vm, SQUserPointer *ptr) { return SQ_SUCCEEDED(sq_getinstanceup(vm, 1, ptr, 0)); }
 
-	HSQUIRRELVM GetVM() {
-#warning Please stop using this function ASAP
-		return this->vm;
-	}
+	/**
+	 * Get the Squirrel-instance pointer.
+	 * @note This will only work just after a function-call from within Squirrel
+	 *  to your C++ function.
+	 */
+	static bool GetInstance(HSQUIRRELVM vm, HSQOBJECT *ptr) { sq_getclass(vm, 1); sq_getstackobj(vm, 1, ptr); return true; }
+
+	/**
+	 * Convert a Squirrel-object to a string.
+	 */
+	static const char *ObjectToString(HSQOBJECT *ptr) { return sq_objtostring(ptr); }
+
+	/**
+	 * Convert a Squirrel-object to an integer.
+	 */
+	static int ObjectToInteger(HSQOBJECT *ptr) { return sq_objtointeger(ptr); }
+
+	/**
+	 * Sets a pointer in the VM that is reasable from where ever you are, what
+	 *  ever your state is. Useful to keep track of the main instance.
+	 */
+	void SetGlobalPointer(void *ptr) { sq_setforeignptr(this->vm, ptr); }
+
+	/**
+	 * Get the pointer as set by SetGlobalPointer.
+	 */
+	static void *GetGlobalPointer(HSQUIRRELVM vm) { return sq_getforeignptr(vm); }
+
+	/**
+	 * Throw a Squirrel error that will be nicely displayed to the user.
+	 */
+	void ThrowError(const char *error) { sq_throwerror(this->vm, error); }
 };
 
 #endif /* SQUIRREL_CORE_HPP */
--- a/src/ai/squirrel/squirrel.cpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/squirrel/squirrel.cpp	Thu Mar 15 13:36:45 2007 +0000
@@ -3,8 +3,6 @@
 /** @file squirrel.cpp allows loading squirrel scripts to control an AI */
 
 #include "squirrel.hpp"
-#include <sqstdio.h>
-#include <sqstdaux.h>
 #include "../../debug.h"
 
 #define SQUIRREL_CLASS
@@ -12,109 +10,134 @@
 #include "squirrel_class.hpp"
 #include "../core/ai_base.hpp"
 #include "../core/ai_company.hpp"
+#include "../core/ai_controller.hpp"
 #include "../core/ai_map.hpp"
 #include "../core/ai_town.hpp"
 
-SQInteger Squirrel::SQDestructor(SQUserPointer p, SQInteger size)
-{
-	return 1;
-}
-
-SQInteger Squirrel::SQConstructor(HSQUIRRELVM vm)
-{
-	DEBUG(ai, 0, "[squirrel] Please do NOT define your AI class yourself; use RegisterAI() instead");
-	return SQ_ERROR;
-}
+static FSquirrel iFSquirrel; ///< Tell the AI-core that we have an AI with which we like to play.
 
-SQInteger Squirrel::SQGetTick(HSQUIRRELVM vm)
-{
-	SQUserPointer self;
+/**
+ * Our tiny wrapper between C++ and Squirrel.
+ */
+class AIFactorySquirrel: public AIFactoryBase {
+public:
+	SquirrelEngine *engine;
+	HSQOBJECT SQ_instance;
 
-	if (!SquirrelEngine::GetInstance(vm, &self)) {
-		DEBUG(ai, 0, "[squirrel] Failed to find instance pointer");
-		return SQ_ERROR;
+	const char   *GetAuthor()      { return this->engine->CallStringMethod (this->SQ_instance, "GetAuthor"); }
+	const char   *GetName()        { return this->engine->CallStringMethod (this->SQ_instance, "GetName"); }
+	const char   *GetDescription() { return this->engine->CallStringMethod (this->SQ_instance, "GetDescription"); }
+	int           GetVersion()     { return this->engine->CallIntegerMethod(this->SQ_instance, "GetVersion"); }
+	const char   *GetDate()        { return this->engine->CallStringMethod (this->SQ_instance, "GetDate"); }
+	AIController *CreateInstance();
+
+	/**
+	 * Register Squirrel script to the Factory.
+	 */
+	void RegisterSquirrel()        { this->RegisterFactory(this->GetName()); }
+
+	/**
+	 * Check if a method exists in the script.
+	 */
+	void CheckMethods(AIFactorySquirrel *fbase, SQInteger *res, const char *name);
+};
+
+void AIFactorySquirrel::CheckMethods(AIFactorySquirrel *fbase, SQInteger *res, const char *name)
+{
+	if (!fbase->engine->MethodExists(fbase->SQ_instance, name)) {
+		char error[1024];
+		snprintf(error, sizeof(error), "Missing method '%s' for FactoryClass", name);
+		fbase->engine->ThrowError(error);
+		*res = SQ_ERROR;
 	}
-
-	/* Return the value */
-	sq_pushinteger(vm, ((Squirrel *)self)->GetTick());
-	return 1;
 }
 
-SQInteger Squirrel::SQRegisterAI(HSQUIRRELVM vm)
+SQInteger FSquirrel::FactoryConstructor(HSQUIRRELVM vm)
 {
-	const SQChar *classname = NULL;
-	sq_getstring(vm, -1, &classname);
-
-	/* Check if the class really exists */
-	int oldtop = sq_gettop(vm);
-	sq_pushroottable(vm);
-	sq_pushstring(vm, classname, -1);
-	if (SQ_FAILED(sq_get(vm, -2))) {
-		DEBUG(ai, 0, "[squirrel] Failed to find class '%s'", classname);
-		return SQ_ERROR;
-	}
-	sq_settop(vm, oldtop);
+	AIFactorySquirrel *fbase = new AIFactorySquirrel();
+	SQInteger res = 0;
 
-	/* Retreive the 'this' pointer from the global space */
-	Squirrel *self = (Squirrel *)sq_getforeignptr(vm);
-	if (self == NULL) {
-		DEBUG(ai, 0, "[squirrel] Currently only one RegisterAI call is allowed!");
-		return SQ_ERROR;
-	}
-	/* Make sure we only register one AI in this script */
-	sq_setforeignptr(vm, NULL);
+	SquirrelEngine::GetInstance(vm, &fbase->SQ_instance);
+	fbase->engine = (SquirrelEngine *)SquirrelEngine::GetGlobalPointer(vm);
 
-	/* Create the new class */
-	if (!self->engine->CreateClassInstance(classname, self, &self->SQ_instance)) {
-		return SQ_ERROR;
-	}
+	/* Check if all needed fields are there */
+	fbase->CheckMethods(fbase, &res, "GetAuthor");
+	fbase->CheckMethods(fbase, &res, "GetName");
+	fbase->CheckMethods(fbase, &res, "GetDescription");
+	fbase->CheckMethods(fbase, &res, "GetVersion");
+	fbase->CheckMethods(fbase, &res, "GetDate");
+	fbase->CheckMethods(fbase, &res, "CreateInstance");
 
-	return 0;
+	/* Only register if all methods were known */
+	if (res == 0) fbase->RegisterSquirrel();
+
+	return res;
 }
 
-void Squirrel::RegisterClasses()
+void FSquirrel::Initializer()
 {
-	HSQUIRRELVM vm = this->engine->GetVM();
-	SQInteger top = sq_gettop(vm);
+	this->engine = new SquirrelEngine();
 
-	/* Store 'this' so we can find it back later on */
-	sq_setforeignptr(vm, this);
-
-	this->engine->AddClassBegin("AIController");
-
-	/* Create a constructor, so we can assign the right 'this' */
-	this->engine->AddMethod("constructor", &Squirrel::SQConstructor, 1, "x");
-	/* Assign other functions as AIController won't be done via the normal way */
-	this->engine->AddMethod("GetTick", &Squirrel::SQGetTick, 1, "x");
-
+	/* Create the AIFactory class, and bind the constructor */
+	this->engine->AddClassBegin("AIFactory");
+	this->engine->AddMethod("constructor", &FSquirrel::FactoryConstructor, 1, "x");
 	this->engine->AddClassEnd();
 
-	/* Register the 'RegisterAI()' function */
-	sq_pushroottable(vm);
-	this->engine->AddMethod("RegisterAI", &Squirrel::SQRegisterAI, 2, "ts");
+	/* Set a dummy AIController, so script can load */
+	this->engine->AddClassBegin("AIController");
+	this->engine->AddClassEnd();
+	/* Mark the engine as global pointer */
+	this->engine->SetGlobalPointer(this->engine);
 
+	/* Now load all scripts we can find */
+	this->engine->LoadScript("ai/SQNoAI/main.nut");
+}
+
+FSquirrel::~FSquirrel()
+{
+	delete this->engine;
+}
+
+AIController *AIFactorySquirrel::CreateInstance()
+{
+	const char *class_name = this->engine->CallStringMethod(this->SQ_instance, "CreateInstance");
+	AIControllerSquirrel *controller = new AIControllerSquirrel("ai/SQNoAI/main.nut", class_name);
+
+	return controller;
+}
+
+void AIControllerSquirrel::RegisterClasses()
+{
+	/* Ignore AIFactory if we are really starting an AI */
+	this->engine->AddClassBegin("AIFactory");
+	this->engine->AddClassEnd();
+
+	/* Register all classes */
+	SQAIControllerRegister(this->engine);
 	SQAIBaseRegister(this->engine);
 	SQAICompanyRegister(this->engine);
 	SQAIMapRegister(this->engine);
 	SQAITownRegister(this->engine);
 
-	/* Reset top */
-	sq_settop(vm, top);
+	this->engine->SetGlobalPointer(this->engine);
 }
 
-Squirrel::Squirrel(const char *script)
+AIControllerSquirrel::AIControllerSquirrel(const char *script, const char *class_name)
 {
 	this->engine = new SquirrelEngine();
 	this->RegisterClasses();
 	this->engine->LoadScript(script);
+	/* Create the main-class */
+	this->engine->CreateClassInstance(class_name, this, &this->SQ_instance);
+	this->engine->CallMethod(this->SQ_instance, "constructor");
 }
 
-Squirrel::~Squirrel()
+AIControllerSquirrel::~AIControllerSquirrel()
 {
 	delete this->engine;
 }
 
-/* virtual */ void Squirrel::GameLoop()
+/* virtual */ void AIControllerSquirrel::GameLoop()
 {
 	this->engine->CallMethod(this->SQ_instance, "GameLoop");
 }
--- a/src/ai/squirrel/squirrel.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/squirrel/squirrel.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -5,7 +5,6 @@
 #ifndef SQUIRREL_HPP
 #define SQUIRREL_HPP
 
-#include "../core/ai_controller.hpp"
 #ifdef _UNICODE
 /* Disable unicode for squirrel to allow compilation with MINGW
  * and simplify coding for WIN32 (squirrel headers miss a lot of "string" functions)
@@ -13,50 +12,46 @@
 #undef _UNICODE
 #endif
 #include <squirrel.h>
+#include "../core/ai_controller.hpp"
+#include "../core/ai_factory.hpp"
 #include "engine.hpp"
 
-class Squirrel: public AIController {
+class AIControllerSquirrel: public AIController {
 private:
-	HSQOBJECT SQ_instance; ///< The internal instance of squirrel
 	SquirrelEngine *engine; ///< The SquirrelEngine
+	HSQOBJECT SQ_instance;  ///< The internal instance of squirrel
 
 	/**
 	 * Registers all our classes, so it can be used from Squirrel.
 	 */
 	void RegisterClasses();
 
-	/**
-	 * Constructor for Squirrel AIController. We assign the real 'this' value to
-	 *  the instance pointer, so we can easy look up this->.
-	 */
-	static SQInteger SQConstructor(HSQUIRRELVM vm);
-
-	/**
-	 * Destructor for Squirrel AIController. This is called if the class is
-	 *  destroyed inside the script.
-	 */
-	static SQInteger SQDestructor(SQUserPointer p, SQInteger size);
+public:
+	AIControllerSquirrel(const char *script_dir, const char *class_name);
+	~AIControllerSquirrel();
 
-	/**
-	 * A wrapper around the AIController class, as that can't be automated.
-	 */
-	static SQInteger SQGetTick(HSQUIRRELVM vm);
+	/* virtual */ void GameLoop();
 
-	/**
-	 * This is called by the init-part of a script to indicate there is an AI class.
-	 */
-	static SQInteger SQRegisterAI(HSQUIRRELVM vm);
+	uint GetTick() { return AIController::GetTick(); }
+};
 
-	/**
-	 * Create a class with the name 'class' and set all pointers correctly.
-	 */
-	bool SQCreateClass(const char *classname);
+class FSquirrel: public AIFactory<FSquirrel> {
+private:
+	SquirrelEngine *engine; ///< The SquirrelEngine
+
+	static SQInteger FactoryConstructor(HSQUIRRELVM vm);
 
 public:
-	Squirrel(const char *script_dir);
-	~Squirrel();
+	~FSquirrel();
 
-	/* virtual */ void GameLoop();
+	const char   *GetAuthor()      { return "OpenTTD Dev Team"; }
+	const char   *GetName()        { return "Squirrel"; }
+	const char   *GetDescription() { return "Squirrel Module for loading scripts"; }
+	int           GetVersion()     { return 0; }
+	const char   *GetDate()        { return ""; }
+	AIController *CreateInstance() { return NULL; }
+	bool          AllowStartup()   { return false; }
+	void          Initializer();
 };
 
 #endif /* SQUIRREL_HPP */
--- a/src/ai/squirrel/squirrel_class.hpp	Thu Mar 15 13:28:11 2007 +0000
+++ b/src/ai/squirrel/squirrel_class.hpp	Thu Mar 15 13:36:45 2007 +0000
@@ -54,6 +54,10 @@
 	void PreRegister(SquirrelEngine *engine)
 	{
 		engine->AddClassBegin(this->classname);
+	}
+
+	void AddConstructor(SquirrelEngine *engine)
+	{
 		engine->AddMethod("constructor", &DefSQClass::Constructor);
 	}