1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-03 09:09:47 +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);
/**
* Interupt VM execution at the start of the next instruction
*/
void interuptNext();
/**
* @brief executes threads until they are all in waiting state.
*/
@ -214,6 +219,7 @@ private:
SCMFile* _file;
SCMOpcodes* _ops;
GameState* state;
bool interupt;
std::vector<SCMThread> _activeThreads;

View File

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

View File

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

View File

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

View File

@ -1,12 +1,69 @@
#include "HttpServer.hpp"
#include <engine/GameState.hpp>
#include <SFML/Network.hpp>
#include <iostream>
#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)
: game(game), world(world)
: game(game), world(world), paused(false)
{}
void HttpServer::run()
@ -48,7 +105,76 @@ void HttpServer::run()
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)
{
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);
void run();
void handleBreakpoint(const SCMBreakpoint& bp);
private:
ReuseableListener listener;
RWGame* game;
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
IngameState::IngameState(RWGame* game, bool newgame, bool test)
: State(game), started(false), newgame(newgame), test(test),
IngameState::IngameState(RWGame* game, bool newgame, const std::string& save)
: State(game), started(false), newgame(newgame), save(save),
autolookTimer(0.f), camMode(IngameState::CAMERA_NORMAL)
{
}
@ -78,11 +78,16 @@ void IngameState::enter()
{
if( newgame )
{
if( test ) {
if( save.empty() )
{
startGame();
}
else if( save == "test" )
{
startTest();
}
else {
startGame();
game->loadGame( save );
}
}
started = true;

View File

@ -18,7 +18,7 @@ class IngameState : public State
};
bool started;
bool test;
std::string save;
bool newgame;
ViewCamera _look;
/** Player input */
@ -28,7 +28,13 @@ class IngameState : public State
float autolookTimer;
CameraMode camMode;
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 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("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("Exit", [=] { getWindow().close(); }));
this->enterMenu(m);