src/ai/squirrel/squirrel.cpp
author truelight
Wed, 14 Mar 2007 10:39:46 +0000
branchnoai
changeset 9378 995e55ab701d
parent 9377 3f1327677bcd
child 9381 df270ca8eec8
permissions -rw-r--r--
(svn r9167) [NoAI] -Fix: store the 'this' pointer in a nicer way (KUDr)
-Fix: don't allow multiple instances of AIController inside one script
/* $Id$ */

/** @file squirrel.cpp allows loading squirrel scripts to control an AI */

#include "squirrel.hpp"
#include <sqstdio.h>
#include <sqstdaux.h>
#include <stdarg.h>
#include "../../debug.h"

/**
 * Here we handle errors that come from squirrel.
 */
static void Squirrel_CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
{
#ifdef _SQ64
	printf("Error %s:%ld/%ld: %s\n", source, line, column, desc);
#else
	printf("Error %s:%d/%d: %s\n", source, line, column, desc);
#endif
}

/**
 * Here we handle run-time errors that come from squirrel.
 */
static SQInteger Squirrel_RunError(HSQUIRRELVM vm)
{
	const SQChar *sErr = 0;

	if (sq_gettop(vm) >= 1) {
		if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) {
			printf("%s\n", sErr);
		} else {
			printf("Unknown error\n");
		}
	}

	return 0;
}

/**
 * Here we handle 'print' command within squirrel script.
 */
static void Squirrel_PrintFunc(HSQUIRRELVM vm, const SQChar *s, ...)
{
	va_list arglist;
	va_start(arglist, s);
	vprintf(s, arglist);
	va_end(arglist);
	printf("\n");
}

SQInteger Squirrel::SQDestructor(SQUserPointer p, SQInteger size)
{
	return 1;
}

SQInteger Squirrel::SQConstructor(HSQUIRRELVM vm)
{
	Squirrel *self = NULL;
	HSQOBJECT object;
	SQUserPointer SQ_instance;

	/* Get the instance pointer */
	sq_getclass(vm, 1);
	sq_getstackobj(vm, 1, &object);
	SQ_instance = object._unVal.pUserPointer;

	/* Find the 'this' pointer and store it inside the class */
	self = (Squirrel *)sq_getforeignptr(vm);
	if (self == NULL) {
		DEBUG(ai, 0, "[squirrel] Please only make one instance of AIController!");
		return SQ_ERROR;
	}
	sq_setinstanceup(vm, 1, self);
	sq_setreleasehook(vm, 1, &Squirrel::SQDestructor);
	/* Keep track of the internal Squirrel instance */
	self->SQ_instance = SQ_instance;

	/* Make sure we only register one AI in this script */
	sq_setforeignptr(vm, NULL);

	return 0;
}

SQInteger Squirrel::SQGetTick(HSQUIRRELVM vm)
{
	Squirrel *self;

	/* Find the 'self' pointer */
	if (SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer *)&self, 0))) {
		DEBUG(ai, 0, "[squirrel] Failed to find the instance-pointer");
		return SQ_ERROR;
	}

	/* Return the value */
	sq_pushinteger(vm, self->GetTick());
	return 1;
}

inline void Squirrel::SQAddMethod(const char *name, SQFUNCTION proc, uint nparam, const char *params)
{
	sq_pushstring(this->vm, name, -1);
	sq_newclosure(this->vm, proc, 0);
	sq_setparamscheck(this->vm, nparam, params);
	sq_setnativeclosurename(this->vm, -1, name);
	sq_createslot(this->vm, -3);
}

void Squirrel::RegisterClasses()
{
	SQInteger top = sq_gettop(this->vm);

	/* Store 'this' so we can find it back later on */
	sq_setforeignptr(this->vm, this);

	/* Create AIController class (the 'main' of it all) */
	sq_pushroottable(this->vm);
	sq_pushstring(this->vm, "AIController", -1);
	sq_newclass(this->vm, SQFalse);

	/* Create a constructor, so we can assign the right 'this' */
	this->SQAddMethod("constructor", &Squirrel::SQConstructor, 1, "x");
	/* Assign other functions as AIController won't be done via the normal way */
	this->SQAddMethod("GetTick", &Squirrel::SQGetTick, 1, "x");

	/* Finally really create the class */
	sq_createslot(this->vm, -3);

	/* Reset top */
	sq_settop(this->vm, top);
}

Squirrel::Squirrel(const char *script_dir)
{
	char filename[1024];

	this->vm = sq_open(1024);

	/* Handle compile-errors ourself, so we can display it nicely */
	sq_setcompilererrorhandler(this->vm, Squirrel_CompileError);
	sq_notifyallexceptions(this->vm, SQTrue);
	/* Set a good print-function */
	sq_setprintfunc(this->vm, Squirrel_PrintFunc);
	/* Handle runtime-errors ourself, so we can display it nicely */
	sq_newclosure(this->vm, &Squirrel_RunError, 0);
	sq_seterrorhandler(this->vm);

	/* TODO -- This is ugly! */
	strcpy(filename, "ai/");
	strcat(filename, script_dir);
	strcat(filename, PATHSEP);
	strcat(filename, "main.sq");

	DEBUG(ai, 1, "[squirrel] Starting AI with script in '%s'", filename);

	sq_pushroottable(this->vm);
	this->RegisterClasses();

	/* Load and run the script */
	if (!SQ_SUCCEEDED(sqstd_dofile(this->vm, filename, SQFalse, SQTrue))) {
		DEBUG(ai, 0, "[squirrel] Failed to compile '%s'", filename);
		return;
	}
}

Squirrel::~Squirrel()
{
	/* Clean up the stuff */
	sq_pop(this->vm, 1);
	sq_close(this->vm);
}

/* virtual */ void Squirrel::GameLoop()
{
	/* Store the current top */
	int top = sq_gettop(this->vm);
	/* Go back to root-table */
	sq_pushroottable(this->vm);
	/* Find the function-name inside the script */
	sq_pushstring(this->vm, "GameLoop", -1);
	if (!SQ_SUCCEEDED(sq_get(this->vm, -2))) {
		DEBUG(ai, 0, "[squirrel] Failed to run GameLoop");
		sq_settop(this->vm, top);
		return;
	}
	/* Call the function */
	sq_pushroottable(this->vm);
	sq_call(this->vm, 1, 0, 0);
	/* Reset the top */
	sq_settop(this->vm, top);
}