(svn r23360) -Codechange: move AIInstance to ScriptInstance, making it reusable by other script API instances

replace/41b28d7194a279bdc17475d4fbe2ea6ec885a466
truebrain 13 years ago
parent 75c4bd280a
commit 3da8b5097a

@ -8852,4 +8852,4 @@ ERROR: IsEnd() is invalid as Begin() is never called
-1 > 2147483647: false
-2147483648 > 2147483647: false
13725 > -2147483648: true
ERROR: The AI died unexpectedly.
ERROR: The script died unexpectedly.

@ -791,6 +791,8 @@
<ClInclude Include="..\src\script\script_fatalerror.hpp" />
<ClCompile Include="..\src\script\script_info.cpp" />
<ClInclude Include="..\src\script\script_info.hpp" />
<ClCompile Include="..\src\script\script_instance.cpp" />
<ClInclude Include="..\src\script\script_instance.hpp" />
<ClCompile Include="..\src\script\script_scanner.cpp" />
<ClInclude Include="..\src\script\script_scanner.hpp" />
<ClInclude Include="..\src\script\script_storage.hpp" />

@ -1596,6 +1596,12 @@
<ClInclude Include="..\src\script\script_info.hpp">
<Filter>Script</Filter>
</ClInclude>
<ClCompile Include="..\src\script\script_instance.cpp">
<Filter>Script</Filter>
</ClCompile>
<ClInclude Include="..\src\script\script_instance.hpp">
<Filter>Script</Filter>
</ClInclude>
<ClCompile Include="..\src\script\script_scanner.cpp">
<Filter>Script</Filter>
</ClCompile>

@ -2470,6 +2470,14 @@
RelativePath=".\..\src\script\script_info.hpp"
>
</File>
<File
RelativePath=".\..\src\script\script_instance.cpp"
>
</File>
<File
RelativePath=".\..\src\script\script_instance.hpp"
>
</File>
<File
RelativePath=".\..\src\script\script_scanner.cpp"
>

@ -2467,6 +2467,14 @@
RelativePath=".\..\src\script\script_info.hpp"
>
</File>
<File
RelativePath=".\..\src\script\script_instance.cpp"
>
</File>
<File
RelativePath=".\..\src\script\script_instance.hpp"
>
</File>
<File
RelativePath=".\..\src\script\script_scanner.cpp"
>

@ -556,6 +556,8 @@ table/water_land.h
script/script_fatalerror.hpp
script/script_info.cpp
script/script_info.hpp
script/script_instance.cpp
script/script_instance.hpp
script/script_scanner.cpp
script/script_scanner.hpp
script/script_storage.hpp

@ -80,105 +80,25 @@
#include "../company_func.h"
#include "../fileio_func.h"
/** The maximum number of operations for saving or loading the data of an AI. */
static const int MAX_SL_OPS = 100000;
/** The maximum number of operations for initial start of an AI. */
static const int MAX_CONSTRUCTOR_OPS = 100000;
ScriptStorage::~ScriptStorage()
{
/* Free our pointers */
if (event_data != NULL) ScriptEventController::FreeEventPointer();
if (log_data != NULL) ScriptLog::FreeLogPointer();
}
/**
* Callback called by squirrel when an AI uses "print" and for error messages.
* @param error_msg Is this an error message?
* @param message The actual message text.
*/
static void PrintFunc(bool error_msg, const SQChar *message)
{
/* Convert to OpenTTD internal capable string */
ScriptController::Print(error_msg, SQ2OTTD(message));
}
AIInstance::AIInstance() :
controller(NULL),
storage(NULL),
engine(NULL),
instance(NULL),
is_started(false),
is_dead(false),
is_save_data_on_stack(false),
suspend(0),
callback(NULL)
{
this->storage = new ScriptStorage();
this->engine = new Squirrel("AI");
this->engine->SetPrintFunction(&PrintFunc);
}
ScriptInstance("AI")
{}
void AIInstance::Initialize(AIInfo *info)
{
ScriptObject::ActiveInstance active(this);
this->controller = new ScriptController();
this->versionAPI = info->GetAPIVersion();
/* Register the AIController (including the "import" command) */
SQAIController_Register(this->engine);
/* Register the API functions and classes */
this->RegisterAPI();
if (!this->LoadCompatibilityScripts(info->GetAPIVersion())) {
this->Died();
return;
}
try {
ScriptObject::SetAllowDoCommand(false);
/* Load and execute the script for this AI */
const char *main_script = info->GetMainScript();
if (strcmp(main_script, "%_dummy") == 0) {
extern void AI_CreateAIDummy(HSQUIRRELVM vm);
AI_CreateAIDummy(this->engine->GetVM());
} else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long to load script. AI is not started.");
this->Died();
return;
}
/* Create the main-class */
this->instance = MallocT<SQObject>(1);
if (!this->engine->CreateClassInstance(info->GetInstanceName(), this->controller, this->instance)) {
this->Died();
return;
}
ScriptObject::SetAllowDoCommand(true);
} catch (Script_FatalError e) {
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
this->Died();
}
}
AIInstance::~AIInstance()
{
ScriptObject::ActiveInstance active(this);
if (instance != NULL) this->engine->ReleaseObject(this->instance);
if (engine != NULL) delete this->engine;
delete this->storage;
delete this->controller;
free(this->instance);
ScriptInstance::Initialize(info->GetMainScript(), info->GetInstanceName());
}
void AIInstance::RegisterAPI()
{
ScriptInstance::RegisterAPI();
/* Register all classes */
squirrel_register_std(this->engine);
SQAIList_Register(this->engine);
SQAIAccounting_Register(this->engine);
SQAIAirport_Register(this->engine);
@ -266,7 +186,7 @@ void AIInstance::RegisterAPI()
SQAIWaypointList_Register(this->engine);
SQAIWaypointList_Vehicle_Register(this->engine);
this->engine->SetGlobalPointer(this->engine);
if (!this->LoadCompatibilityScripts(this->versionAPI)) this->Died();
}
bool AIInstance::LoadCompatibilityScripts(const char *api_version)
@ -291,21 +211,9 @@ bool AIInstance::LoadCompatibilityScripts(const char *api_version)
return true;
}
void AIInstance::Continue()
{
assert(this->suspend < 0);
this->suspend = -this->suspend - 1;
}
void AIInstance::Died()
{
DEBUG(ai, 0, "The AI died unexpectedly.");
this->is_dead = true;
if (this->instance != NULL) this->engine->ReleaseObject(this->instance);
delete this->engine;
this->instance = NULL;
this->engine = NULL;
ScriptInstance::Died();
ShowAIDebugWindow(_current_company);
@ -319,517 +227,3 @@ void AIInstance::Died()
}
}
}
void AIInstance::GameLoop()
{
ScriptObject::ActiveInstance active(this);
if (this->IsDead()) return;
if (this->engine->HasScriptCrashed()) {
/* The script crashed during saving, kill it here. */
this->Died();
return;
}
this->controller->ticks++;
if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
/* If there is a callback to call, call that first */
if (this->callback != NULL) {
if (this->is_save_data_on_stack) {
sq_poptop(this->engine->GetVM());
this->is_save_data_on_stack = false;
}
try {
this->callback(this);
} catch (Script_Suspend e) {
this->suspend = e.GetSuspendTime();
this->callback = e.GetSuspendCallback();
return;
}
}
this->suspend = 0;
this->callback = NULL;
if (!this->is_started) {
try {
ScriptObject::SetAllowDoCommand(false);
/* Run the constructor if it exists. Don't allow any DoCommands in it. */
if (this->engine->MethodExists(*this->instance, "constructor")) {
if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long to initialize. AI is not started.");
this->Died();
return;
}
}
if (!this->CallLoad() || this->engine->IsSuspended()) {
if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long in the Load function. AI is not started.");
this->Died();
return;
}
ScriptObject::SetAllowDoCommand(true);
/* Start the AI by calling Start() */
if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.ai.ai_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
} catch (Script_Suspend e) {
this->suspend = e.GetSuspendTime();
this->callback = e.GetSuspendCallback();
} catch (Script_FatalError e) {
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
this->Died();
}
this->is_started = true;
return;
}
if (this->is_save_data_on_stack) {
sq_poptop(this->engine->GetVM());
this->is_save_data_on_stack = false;
}
/* Continue the VM */
try {
if (!this->engine->Resume(_settings_game.ai.ai_max_opcode_till_suspend)) this->Died();
} catch (Script_Suspend e) {
this->suspend = e.GetSuspendTime();
this->callback = e.GetSuspendCallback();
} catch (Script_FatalError e) {
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
this->Died();
}
}
void AIInstance::CollectGarbage() const
{
if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
}
/* static */ void AIInstance::DoCommandReturn(AIInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
}
/* static */ void AIInstance::DoCommandReturnVehicleID(AIInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetNewVehicleID());
}
/* static */ void AIInstance::DoCommandReturnSignID(AIInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetNewSignID());
}
/* static */ void AIInstance::DoCommandReturnGroupID(AIInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetNewGroupID());
}
ScriptStorage *AIInstance::GetStorage()
{
return this->storage;
}
void *AIInstance::GetLogPointer()
{
ScriptObject::ActiveInstance active(this);
return ScriptObject::GetLogPointer();
}
/*
* All data is stored in the following format:
* First 1 byte indicating if there is a data blob at all.
* 1 byte indicating the type of data.
* The data itself, this differs per type:
* - integer: a binary representation of the integer (int32).
* - string: First one byte with the string length, then a 0-terminated char
* array. The string can't be longer than 255 bytes (including
* terminating '\0').
* - array: All data-elements of the array are saved recursive in this
* format, and ended with an element of the type
* SQSL_ARRAY_TABLE_END.
* - table: All key/value pairs are saved in this format (first key 1, then
* value 1, then key 2, etc.). All keys and values can have an
* arbitrary type (as long as it is supported by the save function
* of course). The table is ended with an element of the type
* SQSL_ARRAY_TABLE_END.
* - bool: A single byte with value 1 representing true and 0 false.
* - null: No data.
*/
/** The type of the data that follows in the savegame. */
enum SQSaveLoadType {
SQSL_INT = 0x00, ///< The following data is an integer.
SQSL_STRING = 0x01, ///< The following data is an string.
SQSL_ARRAY = 0x02, ///< The following data is an array.
SQSL_TABLE = 0x03, ///< The following data is an table.
SQSL_BOOL = 0x04, ///< The following data is a boolean.
SQSL_NULL = 0x05, ///< A null variable.
SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows.
};
static byte _ai_sl_byte; ///< Used as source/target by the AI saveload code to store/load a single byte.
/** SaveLoad array that saves/loads exactly one byte. */
static const SaveLoad _ai_byte[] = {
SLEG_VAR(_ai_sl_byte, SLE_UINT8),
SLE_END()
};
static const uint AISAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame.
/* static */ bool AIInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
{
if (max_depth == 0) {
ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved.");
return false;
}
switch (sq_gettype(vm, index)) {
case OT_INTEGER: {
if (!test) {
_ai_sl_byte = SQSL_INT;
SlObject(NULL, _ai_byte);
}
SQInteger res;
sq_getinteger(vm, index, &res);
if (!test) {
int value = (int)res;
SlArray(&value, 1, SLE_INT32);
}
return true;
}
case OT_STRING: {
if (!test) {
_ai_sl_byte = SQSL_STRING;
SlObject(NULL, _ai_byte);
}
const SQChar *res;
sq_getstring(vm, index, &res);
/* @bug if a string longer than 512 characters is given to SQ2OTTD, the
* internal buffer overflows. */
const char *buf = SQ2OTTD(res);
size_t len = strlen(buf) + 1;
if (len >= 255) {
ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
return false;
}
if (!test) {
_ai_sl_byte = (byte)len;
SlObject(NULL, _ai_byte);
SlArray(const_cast<char *>(buf), len, SLE_CHAR);
}
return true;
}
case OT_ARRAY: {
if (!test) {
_ai_sl_byte = SQSL_ARRAY;
SlObject(NULL, _ai_byte);
}
sq_pushnull(vm);
while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
/* Store the value */
bool res = SaveObject(vm, -1, max_depth - 1, test);
sq_pop(vm, 2);
if (!res) {
sq_pop(vm, 1);
return false;
}
}
sq_pop(vm, 1);
if (!test) {
_ai_sl_byte = SQSL_ARRAY_TABLE_END;
SlObject(NULL, _ai_byte);
}
return true;
}
case OT_TABLE: {
if (!test) {
_ai_sl_byte = SQSL_TABLE;
SlObject(NULL, _ai_byte);
}
sq_pushnull(vm);
while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
/* Store the key + value */
bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
sq_pop(vm, 2);
if (!res) {
sq_pop(vm, 1);
return false;
}
}
sq_pop(vm, 1);
if (!test) {
_ai_sl_byte = SQSL_ARRAY_TABLE_END;
SlObject(NULL, _ai_byte);
}
return true;
}
case OT_BOOL: {
if (!test) {
_ai_sl_byte = SQSL_BOOL;
SlObject(NULL, _ai_byte);
}
SQBool res;
sq_getbool(vm, index, &res);
if (!test) {
_ai_sl_byte = res ? 1 : 0;
SlObject(NULL, _ai_byte);
}
return true;
}
case OT_NULL: {
if (!test) {
_ai_sl_byte = SQSL_NULL;
SlObject(NULL, _ai_byte);
}
return true;
}
default:
ScriptLog::Error("You tried to save an unsupported type. No data saved.");
return false;
}
}
/* static */ void AIInstance::SaveEmpty()
{
_ai_sl_byte = 0;
SlObject(NULL, _ai_byte);
}
void AIInstance::Save()
{
ScriptObject::ActiveInstance active(this);
/* Don't save data if the AI didn't start yet or if it crashed. */
if (this->engine == NULL || this->engine->HasScriptCrashed()) {
SaveEmpty();
return;
}
HSQUIRRELVM vm = this->engine->GetVM();
if (this->is_save_data_on_stack) {
_ai_sl_byte = 1;
SlObject(NULL, _ai_byte);
/* Save the data that was just loaded. */
SaveObject(vm, -1, AISAVE_MAX_DEPTH, false);
} else if (!this->is_started) {
SaveEmpty();
return;
} else if (this->engine->MethodExists(*this->instance, "Save")) {
HSQOBJECT savedata;
/* We don't want to be interrupted during the save function. */
bool backup_allow = ScriptObject::GetAllowDoCommand();
ScriptObject::SetAllowDoCommand(false);
try {
if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
/* The script crashed in the Save function. We can't kill
* it here, but do so in the next AI tick. */
SaveEmpty();
this->engine->CrashOccurred();
return;
}
} catch (Script_FatalError e) {
/* If we don't mark the AI as dead here cleaning up the squirrel
* stack could throw Script_FatalError again. */
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
SaveEmpty();
/* We can't kill the AI here, so mark it as crashed (not dead) and
* kill it in the next AI tick. */
this->is_dead = false;
this->engine->CrashOccurred();
return;
}
ScriptObject::SetAllowDoCommand(backup_allow);
if (!sq_istable(savedata)) {
ScriptLog::Error(this->engine->IsSuspended() ? "This AI took too long to Save." : "Save function should return a table.");
SaveEmpty();
this->engine->CrashOccurred();
return;
}
sq_pushobject(vm, savedata);
if (SaveObject(vm, -1, AISAVE_MAX_DEPTH, true)) {
_ai_sl_byte = 1;
SlObject(NULL, _ai_byte);
SaveObject(vm, -1, AISAVE_MAX_DEPTH, false);
this->is_save_data_on_stack = true;
} else {
SaveEmpty();
this->engine->CrashOccurred();
}
} else {
ScriptLog::Warning("Save function is not implemented");
_ai_sl_byte = 0;
SlObject(NULL, _ai_byte);
}
}
void AIInstance::Suspend()
{
HSQUIRRELVM vm = this->engine->GetVM();
Squirrel::DecreaseOps(vm, _settings_game.ai.ai_max_opcode_till_suspend);
}
/* static */ bool AIInstance::LoadObjects(HSQUIRRELVM vm)
{
SlObject(NULL, _ai_byte);
switch (_ai_sl_byte) {
case SQSL_INT: {
int value;
SlArray(&value, 1, SLE_INT32);
if (vm != NULL) sq_pushinteger(vm, (SQInteger)value);
return true;
}
case SQSL_STRING: {
SlObject(NULL, _ai_byte);
static char buf[256];
SlArray(buf, _ai_sl_byte, SLE_CHAR);
if (vm != NULL) sq_pushstring(vm, OTTD2SQ(buf), -1);
return true;
}
case SQSL_ARRAY: {
if (vm != NULL) sq_newarray(vm, 0);
while (LoadObjects(vm)) {
if (vm != NULL) sq_arrayappend(vm, -2);
/* The value is popped from the stack by squirrel. */
}
return true;
}
case SQSL_TABLE: {
if (vm != NULL) sq_newtable(vm);
while (LoadObjects(vm)) {
LoadObjects(vm);
if (vm != NULL) sq_rawset(vm, -3);
/* The key (-2) and value (-1) are popped from the stack by squirrel. */
}
return true;
}
case SQSL_BOOL: {
SlObject(NULL, _ai_byte);
if (vm != NULL) sq_pushinteger(vm, (SQBool)(_ai_sl_byte != 0));
return true;
}
case SQSL_NULL: {
if (vm != NULL) sq_pushnull(vm);
return true;
}
case SQSL_ARRAY_TABLE_END: {
return false;
}
default: NOT_REACHED();
}
}
/* static */ void AIInstance::LoadEmpty()
{
SlObject(NULL, _ai_byte);
/* Check if there was anything saved at all. */
if (_ai_sl_byte == 0) return;
LoadObjects(NULL);
}
void AIInstance::Load(int version)
{
ScriptObject::ActiveInstance active(this);
if (this->engine == NULL || version == -1) {
LoadEmpty();
return;
}
HSQUIRRELVM vm = this->engine->GetVM();
SlObject(NULL, _ai_byte);
/* Check if there was anything saved at all. */
if (_ai_sl_byte == 0) return;
sq_pushinteger(vm, version);
LoadObjects(vm);
this->is_save_data_on_stack = true;
}
bool AIInstance::CallLoad()
{
HSQUIRRELVM vm = this->engine->GetVM();
/* Is there save data that we should load? */
if (!this->is_save_data_on_stack) return true;
/* Whatever happens, after CallLoad the savegame data is removed from the stack. */
this->is_save_data_on_stack = false;
if (!this->engine->MethodExists(*this->instance, "Load")) {
ScriptLog::Warning("Loading failed: there was data for the AI to load, but the AI does not have a Load() function.");
/* Pop the savegame data and version. */
sq_pop(vm, 2);
return true;
}
/* Go to the instance-root */
sq_pushobject(vm, *this->instance);
/* Find the function-name inside the script */
sq_pushstring(vm, OTTD2SQ("Load"), -1);
/* Change the "Load" string in a function pointer */
sq_get(vm, -2);
/* Push the main instance as "this" object */
sq_pushobject(vm, *this->instance);
/* Push the version data and savegame data as arguments */
sq_push(vm, -5);
sq_push(vm, -5);
/* Call the AI load function. sq_call removes the arguments (but not the
* function pointer) from the stack. */
if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
/* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
sq_pop(vm, 4);
return true;
}
SQInteger AIInstance::GetOpsTillSuspend()
{
return this->engine->GetOpsTillSuspend();
}
void AIInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
{
ScriptObject::ActiveInstance active(this);
ScriptObject::SetLastCommandRes(result.Succeeded());
if (result.Failed()) {
ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
} else {
ScriptObject::IncreaseDoCommandCosts(result.GetCost());
ScriptObject::SetLastCost(result.GetCost());
}
}
void AIInstance::InsertEvent(class ScriptEvent *event)
{
ScriptObject::ActiveInstance active(this);
ScriptEventController::InsertEvent(event);
}

@ -13,19 +13,12 @@
#define AI_INSTANCE_HPP
#include <squirrel.h>
#include "../script/script_suspend.hpp"
#include "../script/script_instance.hpp"
/** Runtime information about an AI like a pointer to the squirrel vm and the current state. */
class AIInstance {
class AIInstance : public ScriptInstance {
public:
friend class ScriptObject;
friend class ScriptController;
/**
* Create a new AI.
*/
AIInstance();
~AIInstance();
/**
* Initialize the AI and prepare it for its first run.
@ -33,164 +26,16 @@ public:
*/
void Initialize(class AIInfo *info);
/**
* An AI in multiplayer waits for the server to handle his DoCommand.
* It keeps waiting for this until this function is called.
*/
void Continue();
/**
* Run the GameLoop of an AI.
*/
void GameLoop();
/**
* Let the VM collect any garbage.
*/
void CollectGarbage() const;
/**
* Get the storage of this AI.
*/
class ScriptStorage *GetStorage();
/**
* Get the log pointer of this AI.
*/
void *GetLogPointer();
/**
* Return a true/false reply for a DoCommand.
*/
static void DoCommandReturn(AIInstance *instance);
/**
* Return a VehicleID reply for a DoCommand.
*/
static void DoCommandReturnVehicleID(AIInstance *instance);
/**
* Return a SignID reply for a DoCommand.
*/
static void DoCommandReturnSignID(AIInstance *instance);
/**
* Return a GroupID reply for a DoCommand.
*/
static void DoCommandReturnGroupID(AIInstance *instance);
/**
* Get the controller attached to the instance.
*/
class ScriptController *GetController() { return controller; }
/**
* Return the "this AI died" value
*/
inline bool IsDead() const { return this->is_dead; }
/**
* Call the AI Save function and save all data in the savegame.
*/
void Save();
/**
* Don't save any data in the savegame.
*/
static void SaveEmpty();
/**
* Load data from a savegame and store it on the stack.
* @param version The version of the AI when saving, or -1 if this was
* not the original AI saving the game.
*/
void Load(int version);
/**
* Load and discard data from a savegame.
*/
static void LoadEmpty();
/**
* Reduces the number of opcodes the AI have left to zero. Unless
* the AI is in a state where it cannot suspend it will be suspended
* for the reminder of the current tick. This function is safe to
* call from within a function called by the AI.
*/
void Suspend();
/**
* Get the number of operations the AI can execute before being suspended.
* This function is safe to call from within a function called by the AI.
* @return The number of operations to execute.
*/
SQInteger GetOpsTillSuspend();
/**
* DoCommand callback function for all commands executed by AIs.
* @param result The result of the command.
* @param tile The tile on which the command was executed.
* @param p1 p1 as given to DoCommandPInternal.
* @param p2 p2 as given to DoCommandPInternal.
*/
void DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2);
/**
* Insert an event for this AI.
* @param event The event to insert.
*/
void InsertEvent(class ScriptEvent *event);
private:
class ScriptController *controller; ///< The AI main class.
class ScriptStorage *storage; ///< Some global information for each running AI.
class Squirrel *engine; ///< A wrapper around the squirrel vm.
SQObject *instance; ///< Squirrel-pointer to the AI main class.
const char *versionAPI; ///< Current API used by this script.
bool is_started; ///< Is the AIs constructor executed?
bool is_dead; ///< True if the AI has been stopped.
bool is_save_data_on_stack; ///< Is the save data still on the squirrel stack?
int suspend; ///< The amount of ticks to suspend this AI before it's allowed to continue.
Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the AI runs.
/**
* Register all API functions to the VM.
*/
void RegisterAPI();
/* virtual */ void RegisterAPI();
/* virtual */ void Died();
/**
* Load squirrel scripts to emulate an older API.
*/
bool LoadCompatibilityScripts(const char *api_version);
/**
* Tell the AI it died.
*/
void Died();
/**
* Call the AI Load function if it exists and data was loaded
* from a savegame.
*/
bool CallLoad();
/**
* Save one object (int / string / array / table) to the savegame.
* @param vm The virtual machine to get all the data from.
* @param index The index on the squirrel stack of the element to save.
* @param max_depth The maximum depth recursive arrays / tables will be stored
* with before an error is returned.
* @param test If true, don't really store the data but only check if it is
* valid.
* @return True if the saving was successful.
*/
static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test);
/**
* Load all objects from a savegame.
* @return True if the loading was successful.
*/
static bool LoadObjects(HSQUIRRELVM vm);
};
#endif /* AI_INSTANCE_HPP */

@ -91,7 +91,6 @@ echo "
/\/\* Register all classes \*\// {
print \$0
gsub(\"^.*/\", \"\")
print \" squirrel_register_std(this->engine);\" \$0
# List needs to be registered with squirrel before all List subclasses.
print \" SQ${apiuc}List_Register(this->engine);\" \$0
split(\"`grep '^void SQ'${apiuc}'.*_Register(Squirrel \*engine)$' *.hpp.sq | grep -v 'SQ'${apiuc}'List_Register' | sed 's/^.*void //;s/Squirrel \*/this->/;s/$/;/;s/_Register/0000Register/g;' | sort | sed 's/0000Register/_Register/g' | tr -d '\r' | tr '\n' ' '`\", regs, \" \")

@ -39,10 +39,10 @@
* Helper function to connect a just built bridge to nearby roads.
* @param instance The AI we have to built the road for.
*/
static void _DoCommandReturnBuildBridge2(class AIInstance *instance)
static void _DoCommandReturnBuildBridge2(class ScriptInstance *instance)
{
if (!ScriptBridge::_BuildBridgeRoad2()) {
AIInstance::DoCommandReturn(instance);
ScriptInstance::DoCommandReturn(instance);
return;
}
@ -55,10 +55,10 @@ static void _DoCommandReturnBuildBridge2(class AIInstance *instance)
* Helper function to connect a just built bridge to nearby roads.
* @param instance The AI we have to built the road for.
*/
static void _DoCommandReturnBuildBridge1(class AIInstance *instance)
static void _DoCommandReturnBuildBridge1(class ScriptInstance *instance)
{
if (!ScriptBridge::_BuildBridgeRoad1()) {
AIInstance::DoCommandReturn(instance);
ScriptInstance::DoCommandReturn(instance);
return;
}

@ -22,7 +22,7 @@
*/
class ScriptController {
friend class AIScanner;
friend class AIInstance;
friend class ScriptInstance;
public:
/**

@ -29,7 +29,7 @@
/* static */ ScriptGroup::GroupID ScriptGroup::CreateGroup(ScriptVehicle::VehicleType vehicle_type)
{
if (!ScriptObject::DoCommand(0, (::VehicleType)vehicle_type, 0, CMD_CREATE_GROUP, NULL, &AIInstance::DoCommandReturnGroupID)) return GROUP_INVALID;
if (!ScriptObject::DoCommand(0, (::VehicleType)vehicle_type, 0, CMD_CREATE_GROUP, NULL, &ScriptInstance::DoCommandReturnGroupID)) return GROUP_INVALID;
/* In case of test-mode, we return GroupID 0 */
return (ScriptGroup::GroupID)0;

@ -22,7 +22,7 @@
#include "script_error.hpp"
/**
* Get the storage associated with the current AIInstance.
* Get the storage associated with the current ScriptInstance.
* @return The storage.
*/
static ScriptStorage *GetStorage()
@ -31,9 +31,9 @@ static ScriptStorage *GetStorage()
}
/* static */ AIInstance *ScriptObject::ActiveInstance::active = NULL;
/* static */ ScriptInstance *ScriptObject::ActiveInstance::active = NULL;
ScriptObject::ActiveInstance::ActiveInstance(AIInstance *instance)
ScriptObject::ActiveInstance::ActiveInstance(ScriptInstance *instance)
{
this->last_active = ScriptObject::ActiveInstance::active;
ScriptObject::ActiveInstance::active = instance;
@ -44,7 +44,7 @@ ScriptObject::ActiveInstance::~ActiveInstance()
ScriptObject::ActiveInstance::active = this->last_active;
}
/* static */ AIInstance *ScriptObject::GetActiveInstance()
/* static */ ScriptInstance *ScriptObject::GetActiveInstance()
{
assert(ScriptObject::ActiveInstance::active != NULL);
return ScriptObject::ActiveInstance::active;
@ -225,14 +225,14 @@ ScriptObject::ActiveInstance::~ActiveInstance()
return GetStorage()->callback_value[index];
}
/* static */ bool ScriptObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback)
/* static */ bool ScriptObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, Script_SuspendCallbackProc *callback)
{
if (!ScriptObject::CanSuspend()) {
throw Script_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator.");
}
/* Set the default callback to return a true/false result of the DoCommand */
if (callback == NULL) callback = &AIInstance::DoCommandReturn;
if (callback == NULL) callback = &ScriptInstance::DoCommandReturn;
/* Are we only interested in the estimate costs? */
bool estimate_only = GetDoCommandMode() != NULL && !GetDoCommandMode()();

@ -17,11 +17,7 @@
#include "../../rail_type.h"
#include "script_types.hpp"
/**
* The callback function when an AI suspends.
*/
typedef void (AISuspendCallbackProc)(class AIInstance *instance);
#include "../script_suspend.hpp"
/**
* The callback function for Mode-classes.
@ -30,12 +26,12 @@ typedef bool (ScriptModeProc)();
/**
* Uper-parent object of all API classes. You should never use this class in
* your AI, as it doesn't publish any public functions. It is used
* your script, as it doesn't publish any public functions. It is used
* internally to have a common place to handle general things, like internal
* command processing, and command-validation checks.
*/
class ScriptObject : public SimpleCountedObject {
friend class AIInstance;
friend class ScriptInstance;
#ifndef DOXYGEN_AI_DOCS
protected:
/**
@ -47,12 +43,12 @@ protected:
class ActiveInstance {
friend class ScriptObject;
public:
ActiveInstance(AIInstance *instance);
ActiveInstance(ScriptInstance *instance);
~ActiveInstance();
private:
AIInstance *last_active; ///< The active instance before we go instantiated.
ScriptInstance *last_active; ///< The active instance before we go instantiated.
static AIInstance *active; ///< The global current active instance.
static ScriptInstance *active; ///< The global current active instance.
};
public:
@ -66,13 +62,13 @@ public:
* Get the currently active instance.
* @return The instance.
*/
static class AIInstance *GetActiveInstance();
static class ScriptInstance *GetActiveInstance();
protected:
/**
* Executes a raw DoCommand for the AI.
*/
static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text = NULL, AISuspendCallbackProc *callback = NULL);
static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text = NULL, Script_SuspendCallbackProc *callback = NULL);
/**
* Sets the DoCommand costs counter to a value.

@ -546,10 +546,10 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr
* between the wanted and the current order.
* @param instance The AI we are doing the callback for.
*/
static void _DoCommandReturnSetOrderFlags(class AIInstance *instance)
static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance)
{
ScriptObject::SetLastCommandRes(ScriptOrder::_SetOrderFlags());
AIInstance::DoCommandReturn(instance);
ScriptInstance::DoCommandReturn(instance);
}
/* static */ bool ScriptOrder::_SetOrderFlags()

@ -69,7 +69,7 @@
EnforcePrecondition(INVALID_SIGN, !::StrEmpty(text));
EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_SIGN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG);
if (!ScriptObject::DoCommand(location, 0, 0, CMD_PLACE_SIGN, text, &AIInstance::DoCommandReturnSignID)) return INVALID_SIGN;
if (!ScriptObject::DoCommand(location, 0, 0, CMD_PLACE_SIGN, text, &ScriptInstance::DoCommandReturnSignID)) return INVALID_SIGN;
/* In case of test-mode, we return SignID 0 */
return 0;

@ -50,10 +50,10 @@
* Helper function to connect a just built tunnel to nearby roads.
* @param instance The AI we have to built the road for.
*/
static void _DoCommandReturnBuildTunnel2(class AIInstance *instance)
static void _DoCommandReturnBuildTunnel2(class ScriptInstance *instance)
{
if (!ScriptTunnel::_BuildTunnelRoad2()) {
AIInstance::DoCommandReturn(instance);
ScriptInstance::DoCommandReturn(instance);
return;
}
@ -66,10 +66,10 @@ static void _DoCommandReturnBuildTunnel2(class AIInstance *instance)
* Helper function to connect a just built tunnel to nearby roads.
* @param instance The AI we have to built the road for.
*/
static void _DoCommandReturnBuildTunnel1(class AIInstance *instance)
static void _DoCommandReturnBuildTunnel1(class ScriptInstance *instance)
{
if (!ScriptTunnel::_BuildTunnelRoad1()) {
AIInstance::DoCommandReturn(instance);
ScriptInstance::DoCommandReturn(instance);
return;
}

@ -60,7 +60,7 @@
EnforcePreconditionCustomError(VEHICLE_INVALID, !ScriptGameSettings::IsDisabledVehicleType((ScriptVehicle::VehicleType)type), ScriptVehicle::ERR_VEHICLE_BUILD_DISABLED);
if (!ScriptObject::DoCommand(depot, engine_id, 0, ::GetCmdBuildVeh(type), NULL, &AIInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
if (!ScriptObject::DoCommand(depot, engine_id, 0, ::GetCmdBuildVeh(type), NULL, &ScriptInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
/* In case of test-mode, we return VehicleID 0 */
return 0;
@ -70,7 +70,7 @@
{
EnforcePrecondition(false, IsValidVehicle(vehicle_id));
if (!ScriptObject::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, NULL, &AIInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
if (!ScriptObject::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, NULL, &ScriptInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
/* In case of test-mode, we return VehicleID 0 */
return 0;

@ -0,0 +1,653 @@
/* $Id$ */
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file script_instance.cpp Implementation of ScriptInstance. */
#include "../stdafx.h"
#include "../debug.h"
#include "../saveload/saveload.h"
#include "../gui.h"
#include "../script/squirrel_class.hpp"
#include "script_fatalerror.hpp"
#include "script_storage.hpp"
#include "script_instance.hpp"
#include "api/script_controller.hpp"
#include "api/script_error.hpp"
#include "api/script_event.hpp"
#include "api/script_log.hpp"
#include "../company_base.h"
#include "../company_func.h"
#include "../fileio_func.h"
/** The maximum number of operations for saving or loading the data of a script. */
static const int MAX_SL_OPS = 100000;
/** The maximum number of operations for initial start of a script. */
static const int MAX_CONSTRUCTOR_OPS = 100000;
ScriptStorage::~ScriptStorage()
{
/* Free our pointers */
if (event_data != NULL) ScriptEventController::FreeEventPointer();
if (log_data != NULL) ScriptLog::FreeLogPointer();
}
/**
* Callback called by squirrel when a script uses "print" and for error messages.
* @param error_msg Is this an error message?
* @param message The actual message text.
*/
static void PrintFunc(bool error_msg, const SQChar *message)
{
/* Convert to OpenTTD internal capable string */
ScriptController::Print(error_msg, SQ2OTTD(message));
}
ScriptInstance::ScriptInstance(const char *APIName) :
engine(NULL),
controller(NULL),
storage(NULL),
instance(NULL),
is_started(false),
is_dead(false),
is_save_data_on_stack(false),
suspend(0),
callback(NULL)
{
this->storage = new ScriptStorage();
this->engine = new Squirrel(APIName);
this->engine->SetPrintFunction(&PrintFunc);
}
void ScriptInstance::Initialize(const char *main_script, const char *instance_name)
{
ScriptObject::ActiveInstance active(this);
this->controller = new ScriptController();
/* Register the API functions and classes */
this->engine->SetGlobalPointer(this->engine);
this->RegisterAPI();
try {
ScriptObject::SetAllowDoCommand(false);
/* Load and execute the script for this script */
if (strcmp(main_script, "%_dummy") == 0) {
extern void AI_CreateAIDummy(HSQUIRRELVM vm);
AI_CreateAIDummy(this->engine->GetVM());
} else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
this->Died();
return;
}
/* Create the main-class */
this->instance = MallocT<SQObject>(1);
if (!this->engine->CreateClassInstance(instance_name, this->controller, this->instance)) {
this->Died();
return;
}
ScriptObject::SetAllowDoCommand(true);
} catch (Script_FatalError e) {
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
this->Died();
}
}
void ScriptInstance::RegisterAPI()
{
squirrel_register_std(this->engine);
}
ScriptInstance::~ScriptInstance()
{
ScriptObject::ActiveInstance active(this);
if (instance != NULL) this->engine->ReleaseObject(this->instance);
if (engine != NULL) delete this->engine;
delete this->storage;
delete this->controller;
free(this->instance);
}
void ScriptInstance::Continue()
{
assert(this->suspend < 0);
this->suspend = -this->suspend - 1;
}
void ScriptInstance::Died()
{
DEBUG(ai, 0, "The script died unexpectedly.");
this->is_dead = true;
if (this->instance != NULL) this->engine->ReleaseObject(this->instance);
delete this->engine;
this->instance = NULL;
this->engine = NULL;
}
void ScriptInstance::GameLoop()
{
ScriptObject::ActiveInstance active(this);
if (this->IsDead()) return;
if (this->engine->HasScriptCrashed()) {
/* The script crashed during saving, kill it here. */
this->Died();
return;
}
this->controller->ticks++;
if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
/* If there is a callback to call, call that first */
if (this->callback != NULL) {
if (this->is_save_data_on_stack) {
sq_poptop(this->engine->GetVM());
this->is_save_data_on_stack = false;
}
try {
this->callback(this);
} catch (Script_Suspend e) {
this->suspend = e.GetSuspendTime();
this->callback = e.GetSuspendCallback();
return;
}
}
this->suspend = 0;
this->callback = NULL;
if (!this->is_started) {
try {
ScriptObject::SetAllowDoCommand(false);
/* Run the constructor if it exists. Don't allow any DoCommands in it. */
if (this->engine->MethodExists(*this->instance, "constructor")) {
if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
this->Died();
return;
}
}
if (!this->CallLoad() || this->engine->IsSuspended()) {
if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
this->Died();
return;
}
ScriptObject::SetAllowDoCommand(true);
/* Start the script by calling Start() */
if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.ai.ai_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
} catch (Script_Suspend e) {
this->suspend = e.GetSuspendTime();
this->callback = e.GetSuspendCallback();
} catch (Script_FatalError e) {
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
this->Died();
}
this->is_started = true;
return;
}
if (this->is_save_data_on_stack) {
sq_poptop(this->engine->GetVM());
this->is_save_data_on_stack = false;
}
/* Continue the VM */
try {
if (!this->engine->Resume(_settings_game.ai.ai_max_opcode_till_suspend)) this->Died();
} catch (Script_Suspend e) {
this->suspend = e.GetSuspendTime();
this->callback = e.GetSuspendCallback();
} catch (Script_FatalError e) {
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
this->Died();
}
}
void ScriptInstance::CollectGarbage() const
{
if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
}
/* static */ void ScriptInstance::DoCommandReturn(ScriptInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
}
/* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetNewVehicleID());
}
/* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetNewSignID());
}
/* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance *instance)
{
instance->engine->InsertResult(ScriptObject::GetNewGroupID());
}
ScriptStorage *ScriptInstance::GetStorage()
{
return this->storage;
}
void *ScriptInstance::GetLogPointer()
{
ScriptObject::ActiveInstance active(this);
return ScriptObject::GetLogPointer();
}
/*
* All data is stored in the following format:
* First 1 byte indicating if there is a data blob at all.
* 1 byte indicating the type of data.
* The data itself, this differs per type:
* - integer: a binary representation of the integer (int32).
* - string: First one byte with the string length, then a 0-terminated char
* array. The string can't be longer than 255 bytes (including
* terminating '\0').
* - array: All data-elements of the array are saved recursive in this
* format, and ended with an element of the type
* SQSL_ARRAY_TABLE_END.
* - table: All key/value pairs are saved in this format (first key 1, then
* value 1, then key 2, etc.). All keys and values can have an
* arbitrary type (as long as it is supported by the save function
* of course). The table is ended with an element of the type
* SQSL_ARRAY_TABLE_END.
* - bool: A single byte with value 1 representing true and 0 false.
* - null: No data.
*/
/** The type of the data that follows in the savegame. */
enum SQSaveLoadType {
SQSL_INT = 0x00, ///< The following data is an integer.
SQSL_STRING = 0x01, ///< The following data is an string.
SQSL_ARRAY = 0x02, ///< The following data is an array.
SQSL_TABLE = 0x03, ///< The following data is an table.
SQSL_BOOL = 0x04, ///< The following data is a boolean.
SQSL_NULL = 0x05, ///< A null variable.
SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows.
};
static byte _script_sl_byte; ///< Used as source/target by the script saveload code to store/load a single byte.
/** SaveLoad array that saves/loads exactly one byte. */
static const SaveLoad _script_byte[] = {
SLEG_VAR(_script_sl_byte, SLE_UINT8),
SLE_END()
};
static const uint SCRIPTSAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame.
/* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
{
if (max_depth == 0) {
ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved.");
return false;
}
switch (sq_gettype(vm, index)) {
case OT_INTEGER: {
if (!test) {
_script_sl_byte = SQSL_INT;
SlObject(NULL, _script_byte);
}
SQInteger res;
sq_getinteger(vm, index, &res);
if (!test) {
int value = (int)res;
SlArray(&value, 1, SLE_INT32);
}
return true;
}
case OT_STRING: {
if (!test) {
_script_sl_byte = SQSL_STRING;
SlObject(NULL, _script_byte);
}
const SQChar *res;
sq_getstring(vm, index, &res);
/* @bug if a string longer than 512 characters is given to SQ2OTTD, the
* internal buffer overflows. */
const char *buf = SQ2OTTD(res);
size_t len = strlen(buf) + 1;
if (len >= 255) {
ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
return false;
}
if (!test) {
_script_sl_byte = (byte)len;
SlObject(NULL, _script_byte);
SlArray(const_cast<char *>(buf), len, SLE_CHAR);
}
return true;
}
case OT_ARRAY: {
if (!test) {
_script_sl_byte = SQSL_ARRAY;
SlObject(NULL, _script_byte);
}
sq_pushnull(vm);
while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
/* Store the value */
bool res = SaveObject(vm, -1, max_depth - 1, test);
sq_pop(vm, 2);
if (!res) {
sq_pop(vm, 1);
return false;
}
}
sq_pop(vm, 1);
if (!test) {
_script_sl_byte = SQSL_ARRAY_TABLE_END;
SlObject(NULL, _script_byte);
}
return true;
}
case OT_TABLE: {
if (!test) {
_script_sl_byte = SQSL_TABLE;
SlObject(NULL, _script_byte);
}
sq_pushnull(vm);
while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
/* Store the key + value */
bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
sq_pop(vm, 2);
if (!res) {
sq_pop(vm, 1);
return false;
}
}
sq_pop(vm, 1);
if (!test) {
_script_sl_byte = SQSL_ARRAY_TABLE_END;
SlObject(NULL, _script_byte);
}
return true;
}
case OT_BOOL: {
if (!test) {
_script_sl_byte = SQSL_BOOL;
SlObject(NULL, _script_byte);
}
SQBool res;
sq_getbool(vm, index, &res);
if (!test) {
_script_sl_byte = res ? 1 : 0;
SlObject(NULL, _script_byte);
}
return true;
}
case OT_NULL: {
if (!test) {
_script_sl_byte = SQSL_NULL;
SlObject(NULL, _script_byte);
}
return true;
}
default:
ScriptLog::Error("You tried to save an unsupported type. No data saved.");
return false;
}
}
/* static */ void ScriptInstance::SaveEmpty()
{
_script_sl_byte = 0;
SlObject(NULL, _script_byte);
}
void ScriptInstance::Save()
{
ScriptObject::ActiveInstance active(this);
/* Don't save data if the script didn't start yet or if it crashed. */
if (this->engine == NULL || this->engine->HasScriptCrashed()) {
SaveEmpty();
return;
}
HSQUIRRELVM vm = this->engine->GetVM();
if (this->is_save_data_on_stack) {
_script_sl_byte = 1;
SlObject(NULL, _script_byte);
/* Save the data that was just loaded. */
SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, false);
} else if (!this->is_started) {
SaveEmpty();
return;
} else if (this->engine->MethodExists(*this->instance, "Save")) {
HSQOBJECT savedata;
/* We don't want to be interrupted during the save function. */
bool backup_allow = ScriptObject::GetAllowDoCommand();
ScriptObject::SetAllowDoCommand(false);
try {
if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
/* The script crashed in the Save function. We can't kill
* it here, but do so in the next script tick. */
SaveEmpty();
this->engine->CrashOccurred();
return;
}
} catch (Script_FatalError e) {
/* If we don't mark the script as dead here cleaning up the squirrel
* stack could throw Script_FatalError again. */
this->is_dead = true;
this->engine->ThrowError(e.GetErrorMessage());
this->engine->ResumeError();
SaveEmpty();
/* We can't kill the script here, so mark it as crashed (not dead) and
* kill it in the next script tick. */
this->is_dead = false;
this->engine->CrashOccurred();
return;
}
ScriptObject::SetAllowDoCommand(backup_allow);
if (!sq_istable(savedata)) {
ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
SaveEmpty();
this->engine->CrashOccurred();
return;
}
sq_pushobject(vm, savedata);
if (SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, true)) {
_script_sl_byte = 1;
SlObject(NULL, _script_byte);
SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, false);
this->is_save_data_on_stack = true;
} else {
SaveEmpty();
this->engine->CrashOccurred();
}
} else {
ScriptLog::Warning("Save function is not implemented");
_script_sl_byte = 0;
SlObject(NULL, _script_byte);
}
}
void ScriptInstance::Suspend()
{
HSQUIRRELVM vm = this->engine->GetVM();
Squirrel::DecreaseOps(vm, _settings_game.ai.ai_max_opcode_till_suspend);
}
/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm)
{
SlObject(NULL, _script_byte);
switch (_script_sl_byte) {
case SQSL_INT: {
int value;
SlArray(&value, 1, SLE_INT32);
if (vm != NULL) sq_pushinteger(vm, (SQInteger)value);
return true;
}
case SQSL_STRING: {
SlObject(NULL, _script_byte);
static char buf[256];
SlArray(buf, _script_sl_byte, SLE_CHAR);
if (vm != NULL) sq_pushstring(vm, OTTD2SQ(buf), -1);
return true;
}
case SQSL_ARRAY: {
if (vm != NULL) sq_newarray(vm, 0);
while (LoadObjects(vm)) {
if (vm != NULL) sq_arrayappend(vm, -2);
/* The value is popped from the stack by squirrel. */
}
return true;
}
case SQSL_TABLE: {
if (vm != NULL) sq_newtable(vm);
while (LoadObjects(vm)) {
LoadObjects(vm);
if (vm != NULL) sq_rawset(vm, -3);
/* The key (-2) and value (-1) are popped from the stack by squirrel. */
}
return true;
}
case SQSL_BOOL: {
SlObject(NULL, _script_byte);
if (vm != NULL) sq_pushinteger(vm, (SQBool)(_script_sl_byte != 0));
return true;
}
case SQSL_NULL: {
if (vm != NULL) sq_pushnull(vm);
return true;
}
case SQSL_ARRAY_TABLE_END: {
return false;
}
default: NOT_REACHED();
}
}
/* static */ void ScriptInstance::LoadEmpty()
{
SlObject(NULL, _script_byte);
/* Check if there was anything saved at all. */
if (_script_sl_byte == 0) return;
LoadObjects(NULL);
}
void ScriptInstance::Load(int version)
{
ScriptObject::ActiveInstance active(this);
if (this->engine == NULL || version == -1) {
LoadEmpty();
return;
}
HSQUIRRELVM vm = this->engine->GetVM();
SlObject(NULL, _script_byte);
/* Check if there was anything saved at all. */
if (_script_sl_byte == 0) return;
sq_pushinteger(vm, version);
LoadObjects(vm);
this->is_save_data_on_stack = true;
}
bool ScriptInstance::CallLoad()
{
HSQUIRRELVM vm = this->engine->GetVM();
/* Is there save data that we should load? */
if (!this->is_save_data_on_stack) return true;
/* Whatever happens, after CallLoad the savegame data is removed from the stack. */
this->is_save_data_on_stack = false;
if (!this->engine->MethodExists(*this->instance, "Load")) {
ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
/* Pop the savegame data and version. */
sq_pop(vm, 2);
return true;
}
/* Go to the instance-root */
sq_pushobject(vm, *this->instance);
/* Find the function-name inside the script */
sq_pushstring(vm, OTTD2SQ("Load"), -1);
/* Change the "Load" string in a function pointer */
sq_get(vm, -2);
/* Push the main instance as "this" object */
sq_pushobject(vm, *this->instance);
/* Push the version data and savegame data as arguments */
sq_push(vm, -5);
sq_push(vm, -5);
/* Call the script load function. sq_call removes the arguments (but not the
* function pointer) from the stack. */
if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
/* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
sq_pop(vm, 4);
return true;
}
SQInteger ScriptInstance::GetOpsTillSuspend()
{
return this->engine->GetOpsTillSuspend();
}
void ScriptInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
{
ScriptObject::ActiveInstance active(this);
ScriptObject::SetLastCommandRes(result.Succeeded());
if (result.Failed()) {
ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
} else {
ScriptObject::IncreaseDoCommandCosts(result.GetCost());
ScriptObject::SetLastCost(result.GetCost());
}
}
void ScriptInstance::InsertEvent(class ScriptEvent *event)
{
ScriptObject::ActiveInstance active(this);
ScriptEventController::InsertEvent(event);
}

@ -0,0 +1,194 @@
/* $Id$ */
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file script_instance.hpp The ScriptInstance tracks a script. */
#ifndef SCRIPT_INSTANCE_HPP
#define SCRIPT_INSTANCE_HPP
#include <squirrel.h>
#include "script_suspend.hpp"
/** Runtime information about a script like a pointer to the squirrel vm and the current state. */
class ScriptInstance {
public:
friend class ScriptObject;
friend class ScriptController;
/**
* Create a new script.
*/
ScriptInstance(const char *APIName);
virtual ~ScriptInstance();
/**
* Initialize the script and prepare it for its first run.
* @param main_script The name of the script to load.
* @param instance_name The name of the instance out of the script to load.
*/
void Initialize(const char *main_script, const char *instance_name);
/**
* A script in multiplayer waits for the server to handle his DoCommand.
* It keeps waiting for this until this function is called.
*/
void Continue();
/**
* Run the GameLoop of a script.
*/
void GameLoop();
/**
* Let the VM collect any garbage.
*/
void CollectGarbage() const;
/**
* Get the storage of this script.
*/
class ScriptStorage *GetStorage();
/**
* Get the log pointer of this script.
*/
void *GetLogPointer();
/**
* Return a true/false reply for a DoCommand.
*/
static void DoCommandReturn(ScriptInstance *instance);
/**
* Return a VehicleID reply for a DoCommand.
*/
static void DoCommandReturnVehicleID(ScriptInstance *instance);
/**
* Return a SignID reply for a DoCommand.
*/
static void DoCommandReturnSignID(ScriptInstance *instance);
/**
* Return a GroupID reply for a DoCommand.
*/
static void DoCommandReturnGroupID(ScriptInstance *instance);
/**
* Get the controller attached to the instance.
*/
class ScriptController *GetController() { return controller; }
/**
* Return the "this script died" value
*/
inline bool IsDead() const { return this->is_dead; }
/**
* Call the script Save function and save all data in the savegame.
*/
void Save();
/**
* Don't save any data in the savegame.
*/
static void SaveEmpty();
/**
* Load data from a savegame and store it on the stack.
* @param version The version of the script when saving, or -1 if this was
* not the original script saving the game.
*/
void Load(int version);
/**
* Load and discard data from a savegame.
*/
static void LoadEmpty();
/**
* Reduces the number of opcodes the script have left to zero. Unless
* the script is in a state where it cannot suspend it will be suspended
* for the reminder of the current tick. This function is safe to
* call from within a function called by the script.
*/
void Suspend();
/**
* Get the number of operations the script can execute before being suspended.
* This function is safe to call from within a function called by the script.
* @return The number of operations to execute.
*/
SQInteger GetOpsTillSuspend();
/**
* DoCommand callback function for all commands executed by scripts.
* @param result The result of the command.
* @param tile The tile on which the command was executed.
* @param p1 p1 as given to DoCommandPInternal.
* @param p2 p2 as given to DoCommandPInternal.
*/
void DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2);
/**
* Insert an event for this script.
* @param event The event to insert.
*/
void InsertEvent(class ScriptEvent *event);
protected:
class Squirrel *engine; ///< A wrapper around the squirrel vm.
/**
* Register all API functions to the VM.
*/
virtual void RegisterAPI();
/**
* Tell the script it died.
*/
virtual void Died();
private:
class ScriptController *controller; ///< The script main class.
class ScriptStorage *storage; ///< Some global information for each running script.
SQObject *instance; ///< Squirrel-pointer to the script main class.
bool is_started; ///< Is the scripts constructor executed?
bool is_dead; ///< True if the script has been stopped.
bool is_save_data_on_stack; ///< Is the save data still on the squirrel stack?
int suspend; ///< The amount of ticks to suspend this script before it's allowed to continue.
Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the script runs.
/**
* Call the script Load function if it exists and data was loaded
* from a savegame.
*/
bool CallLoad();
/**
* Save one object (int / string / array / table) to the savegame.
* @param vm The virtual machine to get all the data from.
* @param index The index on the squirrel stack of the element to save.
* @param max_depth The maximum depth recursive arrays / tables will be stored
* with before an error is returned.
* @param test If true, don't really store the data but only check if it is
* valid.
* @return True if the saving was successful.
*/
static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test);
/**
* Load all objects from a savegame.
* @return True if the loading was successful.
*/
static bool LoadObjects(HSQUIRRELVM vm);
};
#endif /* SCRIPT_INSTANCE_HPP */

@ -15,7 +15,7 @@
/**
* The callback function when a script suspends.
*/
typedef void (Script_SuspendCallbackProc)(class AIInstance *instance);
typedef void (Script_SuspendCallbackProc)(class ScriptInstance *instance);
/**
* A throw-class that is given when the script wants to suspend.

@ -68,7 +68,7 @@ protected:
public:
friend class AIScanner;
friend class AIInstance;
friend class ScriptInstance;
friend class ScriptController;
friend void squirrel_register_std(Squirrel *engine);

Loading…
Cancel
Save