mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-25 11:52:40 +01: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);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user