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:
parent
5344b8bfa1
commit
868883fd36
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user