mirror of https://github.com/OpenTTD/OpenTTD
(svn r17236) [0.7] -Backport from trunk:
- Fix: [NoAI] AIs that crashed during Save() were not killed as they should [FS#3134] (r17231) - Fix: [NoAI] Do not assert when an AI uses AI*Mode objects incorrectly but crash the AI instead (r17230) - Change: [NoAI] Crash an AI when it uses a DoCommand / Sleep instead of just printing an error message in the AI Debug Window [FS#2980] (r17223)release/0.7
parent
91078be657
commit
a0bc64394c
|
@ -286,6 +286,7 @@ SQUIRREL_API void sq_setprintfunc(HSQUIRRELVM v, SQPRINTFUNCTION printfunc);
|
||||||
SQUIRREL_API SQPRINTFUNCTION sq_getprintfunc(HSQUIRRELVM v);
|
SQUIRREL_API SQPRINTFUNCTION sq_getprintfunc(HSQUIRRELVM v);
|
||||||
SQUIRREL_API SQRESULT sq_suspendvm(HSQUIRRELVM v);
|
SQUIRREL_API SQRESULT sq_suspendvm(HSQUIRRELVM v);
|
||||||
SQUIRREL_API bool sq_resumecatch(HSQUIRRELVM v, int suspend = -1);
|
SQUIRREL_API bool sq_resumecatch(HSQUIRRELVM v, int suspend = -1);
|
||||||
|
SQUIRREL_API bool sq_resumeerror(HSQUIRRELVM v);
|
||||||
SQUIRREL_API SQRESULT sq_wakeupvm(HSQUIRRELVM v,SQBool resumedret,SQBool retval,SQBool raiseerror);
|
SQUIRREL_API SQRESULT sq_wakeupvm(HSQUIRRELVM v,SQBool resumedret,SQBool retval,SQBool raiseerror);
|
||||||
SQUIRREL_API SQInteger sq_getvmstate(HSQUIRRELVM v);
|
SQUIRREL_API SQInteger sq_getvmstate(HSQUIRRELVM v);
|
||||||
|
|
||||||
|
|
|
@ -1010,6 +1010,14 @@ bool sq_resumecatch(HSQUIRRELVM v, int suspend)
|
||||||
return v->Execute(_null_, v->_top, -1, -1, ret, SQTrue, SQVM::ET_RESUME_OPENTTD);
|
return v->Execute(_null_, v->_top, -1, -1, ret, SQTrue, SQVM::ET_RESUME_OPENTTD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool sq_resumeerror(HSQUIRRELVM v)
|
||||||
|
{
|
||||||
|
SQObjectPtr ret;
|
||||||
|
v->_can_suspend = true;
|
||||||
|
v->_ops_till_suspend = 1;
|
||||||
|
return v->Execute(_null_, v->_top, -1, -1, ret, SQTrue, SQVM::ET_RESUME_THROW_VM);
|
||||||
|
}
|
||||||
|
|
||||||
void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook)
|
void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook)
|
||||||
{
|
{
|
||||||
if(sq_gettop(v) >= 1){
|
if(sq_gettop(v) >= 1){
|
||||||
|
|
|
@ -128,7 +128,17 @@ public:
|
||||||
}
|
}
|
||||||
void Release() {
|
void Release() {
|
||||||
_uiRef++;
|
_uiRef++;
|
||||||
if (_hook) { _hook(_userpointer,0);}
|
try {
|
||||||
|
if (_hook) { _hook(_userpointer,0);}
|
||||||
|
} catch (...) {
|
||||||
|
_uiRef--;
|
||||||
|
if (_uiRef == 0) {
|
||||||
|
SQInteger size = _memsize;
|
||||||
|
this->~SQInstance();
|
||||||
|
SQ_FREE(this, size);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
_uiRef--;
|
_uiRef--;
|
||||||
if(_uiRef > 0) return;
|
if(_uiRef > 0) return;
|
||||||
SQInteger size = _memsize;
|
SQInteger size = _memsize;
|
||||||
|
|
|
@ -684,10 +684,12 @@ bool SQVM::Execute(SQObjectPtr &closure, SQInteger target, SQInteger nargs, SQIn
|
||||||
break;
|
break;
|
||||||
case ET_RESUME_GENERATOR: _generator(closure)->Resume(this, target); ci->_root = SQTrue; traps += ci->_etraps; break;
|
case ET_RESUME_GENERATOR: _generator(closure)->Resume(this, target); ci->_root = SQTrue; traps += ci->_etraps; break;
|
||||||
case ET_RESUME_VM:
|
case ET_RESUME_VM:
|
||||||
|
case ET_RESUME_THROW_VM:
|
||||||
traps = _suspended_traps;
|
traps = _suspended_traps;
|
||||||
ci->_root = _suspended_root;
|
ci->_root = _suspended_root;
|
||||||
ci->_vargs = _suspend_varargs;
|
ci->_vargs = _suspend_varargs;
|
||||||
_suspended = SQFalse;
|
_suspended = SQFalse;
|
||||||
|
if(et == ET_RESUME_THROW_VM) { SQ_THROW(); }
|
||||||
break;
|
break;
|
||||||
case ET_RESUME_OPENTTD:
|
case ET_RESUME_OPENTTD:
|
||||||
traps = _suspended_traps;
|
traps = _suspended_traps;
|
||||||
|
|
|
@ -53,7 +53,7 @@ struct SQVM : public CHAINABLE_OBJ
|
||||||
|
|
||||||
typedef sqvector<CallInfo> CallInfoVec;
|
typedef sqvector<CallInfo> CallInfoVec;
|
||||||
public:
|
public:
|
||||||
enum ExecutionType { ET_CALL, ET_RESUME_GENERATOR, ET_RESUME_VM, ET_RESUME_OPENTTD };
|
enum ExecutionType { ET_CALL, ET_RESUME_GENERATOR, ET_RESUME_VM, ET_RESUME_THROW_VM, ET_RESUME_OPENTTD };
|
||||||
SQVM(SQSharedState *ss);
|
SQVM(SQSharedState *ss);
|
||||||
~SQVM();
|
~SQVM();
|
||||||
bool Init(SQVM *friendvm, SQInteger stacksize);
|
bool Init(SQVM *friendvm, SQInteger stacksize);
|
||||||
|
|
|
@ -269,7 +269,7 @@ void AIInstance::Died()
|
||||||
|
|
||||||
void AIInstance::GameLoop()
|
void AIInstance::GameLoop()
|
||||||
{
|
{
|
||||||
if (this->is_dead) return;
|
if (this->IsDead()) return;
|
||||||
if (this->engine->HasScriptCrashed()) {
|
if (this->engine->HasScriptCrashed()) {
|
||||||
/* The script crashed during saving, kill it here. */
|
/* The script crashed during saving, kill it here. */
|
||||||
this->Died();
|
this->Died();
|
||||||
|
@ -322,6 +322,11 @@ void AIInstance::GameLoop()
|
||||||
} catch (AI_VMSuspend e) {
|
} catch (AI_VMSuspend e) {
|
||||||
this->suspend = e.GetSuspendTime();
|
this->suspend = e.GetSuspendTime();
|
||||||
this->callback = e.GetSuspendCallback();
|
this->callback = e.GetSuspendCallback();
|
||||||
|
} catch (AI_FatalError e) {
|
||||||
|
this->is_dead = true;
|
||||||
|
this->engine->ThrowError(e.GetErrorMessage());
|
||||||
|
this->engine->ResumeError();
|
||||||
|
this->Died();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->is_started = true;
|
this->is_started = true;
|
||||||
|
@ -338,12 +343,17 @@ void AIInstance::GameLoop()
|
||||||
} catch (AI_VMSuspend e) {
|
} catch (AI_VMSuspend e) {
|
||||||
this->suspend = e.GetSuspendTime();
|
this->suspend = e.GetSuspendTime();
|
||||||
this->callback = e.GetSuspendCallback();
|
this->callback = e.GetSuspendCallback();
|
||||||
|
} catch (AI_FatalError e) {
|
||||||
|
this->is_dead = true;
|
||||||
|
this->engine->ThrowError(e.GetErrorMessage());
|
||||||
|
this->engine->ResumeError();
|
||||||
|
this->Died();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIInstance::CollectGarbage()
|
void AIInstance::CollectGarbage()
|
||||||
{
|
{
|
||||||
if (this->is_started && !this->is_dead) this->engine->CollectGarbage();
|
if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */ void AIInstance::DoCommandReturn(AIInstance *instance)
|
/* static */ void AIInstance::DoCommandReturn(AIInstance *instance)
|
||||||
|
@ -562,10 +572,25 @@ void AIInstance::Save()
|
||||||
/* We don't want to be interrupted during the save function. */
|
/* We don't want to be interrupted during the save function. */
|
||||||
bool backup_allow = AIObject::GetAllowDoCommand();
|
bool backup_allow = AIObject::GetAllowDoCommand();
|
||||||
AIObject::SetAllowDoCommand(false);
|
AIObject::SetAllowDoCommand(false);
|
||||||
if (!this->engine->CallMethod(*this->instance, "Save", &savedata)) {
|
try {
|
||||||
/* The script crashed in the Save function. We can't kill
|
if (!this->engine->CallMethod(*this->instance, "Save", &savedata)) {
|
||||||
* it here, but do so in the next AI tick. */
|
/* 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 (AI_FatalError e) {
|
||||||
|
/* If we don't mark the AI as dead here cleaning up the squirrel
|
||||||
|
* stack could throw AI_FatalError again. */
|
||||||
|
this->is_dead = true;
|
||||||
|
this->engine->ThrowError(e.GetErrorMessage());
|
||||||
|
this->engine->ResumeError();
|
||||||
SaveEmpty();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
AIObject::SetAllowDoCommand(backup_allow);
|
AIObject::SetAllowDoCommand(backup_allow);
|
||||||
|
|
|
@ -18,7 +18,7 @@ public:
|
||||||
AI_VMSuspend(int time, AISuspendCallbackProc *callback) :
|
AI_VMSuspend(int time, AISuspendCallbackProc *callback) :
|
||||||
time(time),
|
time(time),
|
||||||
callback(callback)
|
callback(callback)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
int GetSuspendTime() { return time; }
|
int GetSuspendTime() { return time; }
|
||||||
AISuspendCallbackProc *GetSuspendCallback() { return callback; }
|
AISuspendCallbackProc *GetSuspendCallback() { return callback; }
|
||||||
|
@ -28,6 +28,21 @@ private:
|
||||||
AISuspendCallbackProc *callback;
|
AISuspendCallbackProc *callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A throw-class that is given when the AI made a fatal error.
|
||||||
|
*/
|
||||||
|
class AI_FatalError {
|
||||||
|
public:
|
||||||
|
AI_FatalError(const char *msg) :
|
||||||
|
msg(msg)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char *GetErrorMessage() { return msg; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char *msg;
|
||||||
|
};
|
||||||
|
|
||||||
class AIInstance {
|
class AIInstance {
|
||||||
public:
|
public:
|
||||||
friend class AIObject;
|
friend class AIObject;
|
||||||
|
@ -80,6 +95,11 @@ public:
|
||||||
*/
|
*/
|
||||||
class AIController *GetController() { return controller; }
|
class AIController *GetController() { return controller; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the "this AI died" value
|
||||||
|
*/
|
||||||
|
inline bool IsDead() { return this->is_dead; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call the AI Save function and save all data in the savegame.
|
* Call the AI Save function and save all data in the savegame.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -24,8 +24,7 @@
|
||||||
/* static */ void AIController::Sleep(int ticks)
|
/* static */ void AIController::Sleep(int ticks)
|
||||||
{
|
{
|
||||||
if (!AIObject::GetAllowDoCommand()) {
|
if (!AIObject::GetAllowDoCommand()) {
|
||||||
AILog::Error("You are not allowed to call Sleep in your constructor, Save(), Load(), and any valuator.\n");
|
throw AI_FatalError("You are not allowed to call Sleep in your constructor, Save(), Load(), and any valuator.");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ticks <= 0) {
|
if (ticks <= 0) {
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
#include "ai_execmode.hpp"
|
#include "ai_execmode.hpp"
|
||||||
#include "../../command_type.h"
|
#include "../../command_type.h"
|
||||||
|
#include "../../company_base.h"
|
||||||
|
#include "../../company_func.h"
|
||||||
|
#include "../ai_instance.hpp"
|
||||||
|
|
||||||
bool AIExecMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs)
|
bool AIExecMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs)
|
||||||
{
|
{
|
||||||
|
@ -21,6 +24,12 @@ AIExecMode::AIExecMode()
|
||||||
|
|
||||||
AIExecMode::~AIExecMode()
|
AIExecMode::~AIExecMode()
|
||||||
{
|
{
|
||||||
assert(this->GetDoCommandModeInstance() == this);
|
if (this->GetDoCommandModeInstance() != this) {
|
||||||
|
AIInstance *instance = GetCompany(_current_company)->ai_instance;
|
||||||
|
/* Ignore this error if the AI already died. */
|
||||||
|
if (!instance->IsDead()) {
|
||||||
|
throw AI_FatalError("AIExecMode object was removed while it was not the latest AI*Mode object created.");
|
||||||
|
}
|
||||||
|
}
|
||||||
this->SetDoCommandMode(this->last_mode, this->last_instance);
|
this->SetDoCommandMode(this->last_mode, this->last_instance);
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,8 +190,7 @@ int AIObject::GetCallbackVariable(int index)
|
||||||
bool AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback)
|
bool AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback)
|
||||||
{
|
{
|
||||||
if (AIObject::GetAllowDoCommand() == false) {
|
if (AIObject::GetAllowDoCommand() == false) {
|
||||||
AILog::Error("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator.\n");
|
throw AI_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator.");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandCost res;
|
CommandCost res;
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
#include "ai_testmode.hpp"
|
#include "ai_testmode.hpp"
|
||||||
#include "../../command_type.h"
|
#include "../../command_type.h"
|
||||||
|
#include "../../company_base.h"
|
||||||
|
#include "../../company_func.h"
|
||||||
|
#include "../ai_instance.hpp"
|
||||||
|
|
||||||
bool AITestMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs)
|
bool AITestMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs)
|
||||||
{
|
{
|
||||||
|
@ -21,6 +24,12 @@ AITestMode::AITestMode()
|
||||||
|
|
||||||
AITestMode::~AITestMode()
|
AITestMode::~AITestMode()
|
||||||
{
|
{
|
||||||
assert(this->GetDoCommandModeInstance() == this);
|
if (this->GetDoCommandModeInstance() != this) {
|
||||||
|
AIInstance *instance = GetCompany(_current_company)->ai_instance;
|
||||||
|
/* Ignore this error if the AI already died. */
|
||||||
|
if (!instance->IsDead()) {
|
||||||
|
throw AI_FatalError("AITestmode object was removed while it was not the latest AI*Mode object created.");
|
||||||
|
}
|
||||||
|
}
|
||||||
this->SetDoCommandMode(this->last_mode, this->last_instance);
|
this->SetDoCommandMode(this->last_mode, this->last_instance);
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,6 +187,12 @@ bool Squirrel::Resume(int suspend)
|
||||||
return this->vm->_suspended != 0;
|
return this->vm->_suspended != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Squirrel::ResumeError()
|
||||||
|
{
|
||||||
|
assert(!this->crashed);
|
||||||
|
sq_resumeerror(this->vm);
|
||||||
|
}
|
||||||
|
|
||||||
void Squirrel::CollectGarbage()
|
void Squirrel::CollectGarbage()
|
||||||
{
|
{
|
||||||
sq_collectgarbage(this->vm);
|
sq_collectgarbage(this->vm);
|
||||||
|
|
|
@ -106,6 +106,11 @@ public:
|
||||||
*/
|
*/
|
||||||
bool Resume(int suspend = -1);
|
bool Resume(int suspend = -1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume the VM with an error so it prints a stack trace.
|
||||||
|
*/
|
||||||
|
void ResumeError();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell the VM to do a garbage collection run.
|
* Tell the VM to do a garbage collection run.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue