1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-15 15:02:34 +02:00

Add Debugger functionality

* Include Angluar JS for UI
* Improve styling slightly
* Add Interrupt/Continue buttons and api URLs
* List of threads when game is paused
This commit is contained in:
Daniel Evans 2015-07-03 02:52:43 +01:00
parent 5344b8bfa1
commit 868883fd36
9 changed files with 186 additions and 16 deletions

View File

@ -205,6 +205,11 @@ public:
*/ */
void removeBreakpoint(SCMThread::pc_t pc); void removeBreakpoint(SCMThread::pc_t pc);
/**
* Interupt VM execution at the start of the next instruction
*/
void interuptNext();
/** /**
* @brief executes threads until they are all in waiting state. * @brief executes threads until they are all in waiting state.
*/ */
@ -214,6 +219,7 @@ private:
SCMFile* _file; SCMFile* _file;
SCMOpcodes* _ops; SCMOpcodes* _ops;
GameState* state; GameState* state;
bool interupt;
std::vector<SCMThread> _activeThreads; std::vector<SCMThread> _activeThreads;

View File

@ -191,6 +191,7 @@ InstanceObject *GameWorld::createInstance(const uint16_t id, const glm::vec3& po
); );
instancePool.insert(instance); instancePool.insert(instance);
allObjects.push_back(instance);
if( shouldBeOnGrid(instance) ) if( shouldBeOnGrid(instance) )
{ {
@ -304,6 +305,7 @@ CutsceneObject *GameWorld::createCutsceneObject(const uint16_t id, const glm::ve
m); m);
cutscenePool.insert( instance ); cutscenePool.insert( instance );
allObjects.push_back(instance);
return instance; return instance;
@ -369,6 +371,7 @@ VehicleObject *GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos,
vehicle->setGameObjectID(gid); vehicle->setGameObjectID(gid);
vehiclePool.insert( vehicle ); vehiclePool.insert( vehicle );
allObjects.push_back( vehicle );
return vehicle; return vehicle;
} }
@ -410,6 +413,7 @@ CharacterObject* GameWorld::createPedestrian(const uint16_t id, const glm::vec3&
ped->setGameObjectID(gid); ped->setGameObjectID(gid);
new DefaultAIController(ped); new DefaultAIController(ped);
pedestrianPool.insert( ped ); pedestrianPool.insert( ped );
allObjects.push_back( ped );
return ped; return ped;
} }
} }
@ -437,6 +441,7 @@ CharacterObject* GameWorld::createPlayer(const glm::vec3& pos, const glm::quat&
ped->setLifetime(GameObject::PlayerLifetime); ped->setLifetime(GameObject::PlayerLifetime);
players.push_back(new PlayerController(ped)); players.push_back(new PlayerController(ped));
pedestrianPool.insert(ped); pedestrianPool.insert(ped);
allObjects.push_back( ped );
return ped; return ped;
} }
} }
@ -510,6 +515,8 @@ void GameWorld::destroyObject(GameObject* object)
auto& pool = getTypeObjectPool(object); auto& pool = getTypeObjectPool(object);
pool.remove(object); pool.remove(object);
allObjects.erase(std::find(allObjects.begin(), allObjects.end(), object));
delete object; delete object;
} }

View File

@ -31,6 +31,11 @@ bool SCMOpcodes::findOpcode(ScriptFunctionID id, ScriptFunctionMeta** out)
return false; return false;
} }
void ScriptMachine::interuptNext()
{
interupt = true;
}
#include <iostream> #include <iostream>
void ScriptMachine::executeThread(SCMThread &t, int msPassed) void ScriptMachine::executeThread(SCMThread &t, int msPassed)
{ {
@ -134,8 +139,9 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
if( hasDebugging ) if( hasDebugging )
{ {
if( breakpoints.find(pc) != breakpoints.end() ) if( breakpoints.find(pc) != breakpoints.end() || interupt )
{ {
interupt = false;
SCMBreakpoint bp; SCMBreakpoint bp;
bp.pc = pc; bp.pc = pc;
bp.thread = &t; bp.thread = &t;
@ -189,7 +195,7 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
} }
ScriptMachine::ScriptMachine(GameState* _state, SCMFile *file, SCMOpcodes *ops) ScriptMachine::ScriptMachine(GameState* _state, SCMFile *file, SCMOpcodes *ops)
: _file(file), _ops(ops), state(_state) : _file(file), _ops(ops), state(_state), interupt(false)
{ {
auto globals = _file->getGlobalsSize(); auto globals = _file->getGlobalsSize();
globalData.resize(globals); globalData.resize(globals);

View File

@ -37,6 +37,7 @@ RWGame::RWGame(const std::string& gamepath, int argc, char* argv[])
bool newgame = false; bool newgame = false;
bool test = false; bool test = false;
bool debugscript = false; bool debugscript = false;
std::string startSave;
for( int i = 1; i < argc; ++i ) for( int i = 1; i < argc; ++i )
{ {
@ -64,6 +65,10 @@ RWGame::RWGame(const std::string& gamepath, int argc, char* argv[])
{ {
debugscript = true; debugscript = true;
} }
if( strcmp( "--load", argv[i] ) == 0 && i+1 < argc )
{
startSave = argv[i+1];
}
} }
@ -129,8 +134,12 @@ RWGame::RWGame(const std::string& gamepath, int argc, char* argv[])
auto loading = new LoadingState(this); auto loading = new LoadingState(this);
if( newgame ) if( newgame )
{ {
loading->setNextState(new IngameState(this,true,test)); loading->setNextState(new IngameState(this,true, "test"));
} }
else if( ! startSave.empty() )
{
loading->setNextState(new IngameState(this,true, startSave));
}
else else
{ {
loading->setNextState(new MenuState(this)); loading->setNextState(new MenuState(this));
@ -185,6 +194,8 @@ void RWGame::loadGame(const std::string& savename)
delete state->script; delete state->script;
state = nullptr; state = nullptr;
log.info("Game", "Loading game " + savename);
newGame(); newGame();
startScript("data/main.scm"); startScript("data/main.scm");
@ -219,7 +230,7 @@ void RWGame::startScript(const std::string& name)
script->setBreakpointHandler( script->setBreakpointHandler(
[&](const SCMBreakpoint& bp) [&](const SCMBreakpoint& bp)
{ {
log.info("Script", "Breakpoint hit!"); log.info("Script", "Breakpoint hit!");
std::stringstream ss; std::stringstream ss;
ss << " " << bp.function->description << "."; ss << " " << bp.function->description << ".";
ss << " Args:"; ss << " Args:";
@ -234,8 +245,8 @@ void RWGame::startScript(const std::string& name)
} }
log.info("Script", ss.str()); log.info("Script", ss.str());
}); httpserver->handleBreakpoint(bp);
script->addBreakpoint(0); });
state->script = script; state->script = script;
} }
else { else {

View File

@ -1,12 +1,69 @@
#include "HttpServer.hpp" #include "HttpServer.hpp"
#include <engine/GameState.hpp>
#include <SFML/Network.hpp> #include <SFML/Network.hpp>
#include <iostream> #include <iostream>
#include <regex> #include <regex>
const char* src_debugger_js = R"(
var app = angular.module('debugApp', []);
app.controller('DebugCtrl', function($scope,$http) {
$scope.threads = [];
$scope.running = true;
$scope.refresh = function() {
var promise = $http.get('/state')
.success(function(data, status, headers, config) {
$scope.running = data.status == 'running';
$scope.threads = data.threads;
});
};
$scope.interrupt = function() {
var promise = $http.get('/interrupt')
.success(function(data, status, headers, config) {
$scope.refresh();
});
}
$scope.continue = function() {
var promise = $http.get('/continue')
.success(function(data, status, headers, config) {
$scope.refresh();
});
}
$scope.refresh();
});
)";
const char* src_page = R"(
<html ng-app="debugApp">
<head>
<style type="text/css">
html,body { font-family: sans-serif; }
</style>
</style>
<body ng-controller="DebugCtrl">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
<h1>OpenRW Debugger</h1>
<button ng-click="interrupt();">Interrupt</button> <button ng-click="continue();">Continue</button>
<span id="running" style="background: green; color: white; padding: 2px; border-radius: 2px" ng-show="running">Game is running</span>
<div>
<h2>Threads</h2>
<ul>
<li ng-repeat="thread in threads">
<h3>{{thread.name}}</h3>
<i>program counter: </i> {{thread.program_counter}}<br>
<i>wake counter: </i> {{thread.wake_counter}} ms<br>
</li>
</ul>
</div>
<div><textarea id="state_dump"></textarea></div>
<script src="/debugger.js"></script>
</body>
</html>)";
HttpServer::HttpServer(RWGame* game, GameWorld* world) HttpServer::HttpServer(RWGame* game, GameWorld* world)
: game(game), world(world) : game(game), world(world), paused(false)
{} {}
void HttpServer::run() void HttpServer::run()
@ -48,7 +105,76 @@ void HttpServer::run()
listener.close(); listener.close();
} }
void HttpServer::handleBreakpoint(const SCMBreakpoint &bp)
{
paused = true;
while( paused ) {
// Do nothing
}
}
std::string HttpServer::getState() const
{
if( !paused ) {
return R"({"status":"running"})";
}
else {
std::stringstream ss;
ss << "{";
ss << R"("status":"interrupted",)";
ss << R"("threads": [)";
for(unsigned int i = 0; i < game->getScript()->getThreads().size(); ++i)
{
SCMThread& th = game->getScript()->getThreads()[i];
bool last = (game->getScript()->getThreads().size() == i+1);
ss << "{"
<< "\"program_counter\": " << th.programCounter << ","
<< "\"name\": \"" << th.name << "\","
<< "\"wake_counter\": " << th.wakeCounter << ""
<< (last ? "}" : "},");
}
ss << R"(])";
ss << "}";
return ss.str();
}
}
std::string HttpServer::dispatch(std::string method, std::string path) std::string HttpServer::dispatch(std::string method, std::string path)
{ {
return "HTTP/1.1 200 OK\n\n<h1>HELLO FROM OPENRW</h1>"; std::stringstream ss;
std::string mime = "text/html";
if(path == "/debugger.js") {
ss << src_debugger_js;
mime = "application/javascript";
}
else if(path == "/state") {
ss << getState();
mime = "application/json";
}
else if(path == "/interrupt") {
game->getScript()->interuptNext();
/* Block until paused is true */
while( ! paused ) { std::this_thread::yield(); }
ss << getState();
mime = "application/json";
}
else if(path == "/continue") {
paused = false;
ss << getState();
mime = "application/json";
}
else if(path == "/") {
ss << src_page;
}
else {
ss << "HTTP/1.1 404 Not Found\n\n";
return ss.str();
}
std::stringstream outs;
outs << "HTTP/1.1 200 OK\n"
<< "Content-Type: " << mime<< "\n"
<< "Connection: Close\n"
<< "\n" << ss.str();
return outs.str();
} }

View File

@ -25,10 +25,13 @@ public:
HttpServer(RWGame* game, GameWorld* world); HttpServer(RWGame* game, GameWorld* world);
void run(); void run();
void handleBreakpoint(const SCMBreakpoint& bp);
private: private:
ReuseableListener listener; ReuseableListener listener;
RWGame* game; RWGame* game;
GameWorld* world; GameWorld* world;
bool paused;
std::string dispatch(std::string method, std::string path); std::string dispatch(std::string method, std::string path);
std::string getState() const;
}; };

View File

@ -17,8 +17,8 @@
#define AUTOLOOK_TIME 2.f #define AUTOLOOK_TIME 2.f
IngameState::IngameState(RWGame* game, bool newgame, bool test) IngameState::IngameState(RWGame* game, bool newgame, const std::string& save)
: State(game), started(false), newgame(newgame), test(test), : State(game), started(false), newgame(newgame), save(save),
autolookTimer(0.f), camMode(IngameState::CAMERA_NORMAL) autolookTimer(0.f), camMode(IngameState::CAMERA_NORMAL)
{ {
} }
@ -78,11 +78,16 @@ void IngameState::enter()
{ {
if( newgame ) if( newgame )
{ {
if( test ) { if( save.empty() )
{
startGame();
}
else if( save == "test" )
{
startTest(); startTest();
} }
else { else {
startGame(); game->loadGame( save );
} }
} }
started = true; started = true;

View File

@ -18,7 +18,7 @@ class IngameState : public State
}; };
bool started; bool started;
bool test; std::string save;
bool newgame; bool newgame;
ViewCamera _look; ViewCamera _look;
/** Player input */ /** Player input */
@ -28,7 +28,13 @@ class IngameState : public State
float autolookTimer; float autolookTimer;
CameraMode camMode; CameraMode camMode;
public: public:
IngameState(RWGame* game, bool newgame = true, bool test = false); /**
* @brief IngameState
* @param game
* @param newgame
* @param game An empty string, a save game to load, or the string "test".
*/
IngameState(RWGame* game, bool newgame = true, const std::string& save = "");
void startTest(); void startTest();
void startGame(); void startGame();

View File

@ -21,7 +21,7 @@ void MenuState::enterMainMenu()
})); }));
m->addEntry(Menu::lambda("Start", [=] { StateManager::get().enter(new IngameState(game)); })); m->addEntry(Menu::lambda("Start", [=] { StateManager::get().enter(new IngameState(game)); }));
m->addEntry(Menu::lambda("Load Game", [=] { enterLoadMenu(); })); m->addEntry(Menu::lambda("Load Game", [=] { enterLoadMenu(); }));
m->addEntry(Menu::lambda("Test", [=] { StateManager::get().enter(new IngameState(game, true, true)); })); m->addEntry(Menu::lambda("Test", [=] { StateManager::get().enter(new IngameState(game, true, "true")); }));
m->addEntry(Menu::lambda("Options", [] { std::cout << "Options" << std::endl; })); m->addEntry(Menu::lambda("Options", [] { std::cout << "Options" << std::endl; }));
m->addEntry(Menu::lambda("Exit", [=] { getWindow().close(); })); m->addEntry(Menu::lambda("Exit", [=] { getWindow().close(); }));
this->enterMenu(m); this->enterMenu(m);