Browse Source

Add debug console

master
C. J. Howard 5 years ago
parent
commit
eff3c19950
Signed by: cjhoward GPG Key ID: 03E1FABA9C3EC195
6 changed files with 363 additions and 41 deletions
  1. +99
    -0
      src/debug/console.cpp
  2. +148
    -0
      src/debug/console.hpp
  3. +95
    -29
      src/game.cpp
  4. +9
    -7
      src/game.hpp
  5. +0
    -3
      src/game/forceps.cpp
  6. +12
    -2
      src/resources/string-table.hpp

+ 99
- 0
src/debug/console.cpp View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2017-2019 Christopher J. Howard
*
* This file is part of Antkeeper Source Code.
*
* Antkeeper Source Code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper Source Code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper Source Code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "console.hpp"
#include <sstream>
template<>
int ArgumentParser<int>::parse(const std::string& argument)
{
return std::stoi(argument);
}
template<>
unsigned int ArgumentParser<unsigned int>::parse(const std::string& argument)
{
return static_cast<unsigned int>(std::stoul(argument));
}
template<>
long ArgumentParser<long>::parse(const std::string& argument)
{
return std::stol(argument);
}
template<>
unsigned long ArgumentParser<unsigned long>::parse(const std::string& argument)
{
return std::stoul(argument);
}
template<>
float ArgumentParser<float>::parse(const std::string& argument)
{
return std::stof(argument);
}
template<>
double ArgumentParser<double>::parse(const std::string& argument)
{
return std::stod(argument);
}
template<>
std::string ArgumentParser<std::string>::parse(const std::string& argument)
{
return argument;
}
std::tuple<std::string, std::vector<std::string>, std::function<void()>> CommandInterpreter::interpret(const std::string& line)
{
// Split line into arguments
std::vector<std::string> arguments;
std::stringstream stream(line);
std::string argument;
while (std::getline(stream, argument, ' '))
{
arguments.push_back(argument);
}
if (arguments.empty())
{
return {std::string(), std::vector<std::string>(), nullptr};
}
// Get command name
std::string name = arguments[0];
// Remove command name from arguments
arguments.erase(arguments.begin());
// Find command linker for this command
auto linker = linkers.find(name);
if (linker == linkers.end())
{
return {name, arguments, nullptr};
}
// Link command function and its arguments into a callable object
std::function<void()> call = linker->second(arguments);
return {name, arguments, call};
}

+ 148
- 0
src/debug/console.hpp View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2017-2019 Christopher J. Howard
*
* This file is part of Antkeeper Source Code.
*
* Antkeeper Source Code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper Source Code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper Source Code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CONSOLE_HPP
#define CONSOLE_HPP
#include <functional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
/**
* Parses an argument string into a typed value.
*/
template<class T>
class ArgumentParser
{
public:
static T parse(const std::string& argument);
};
template<>
int ArgumentParser<int>::parse(const std::string& argument);
template<>
unsigned int ArgumentParser<unsigned int>::parse(const std::string& argument);
template<>
long ArgumentParser<long>::parse(const std::string& argument);
template<>
unsigned long ArgumentParser<unsigned long>::parse(const std::string& argument);
template<>
float ArgumentParser<float>::parse(const std::string& argument);
template<>
double ArgumentParser<double>::parse(const std::string& argument);
template<>
std::string ArgumentParser<std::string>::parse(const std::string& argument);
/**
* Parses an argument vector of strings into a tuple of typed values.
*/
class ArgumentVectorParser
{
public:
template<class... Args>
static std::tuple<Args...> parse(const std::vector<std::string>& arguments)
{
return parse<Args...>(arguments, std::make_index_sequence<sizeof...(Args)>{});
}
private:
template <class... Args, std::size_t... index>
static std::tuple<Args...> parse(const std::vector<std::string>& arguments, std::index_sequence<index...>)
{
return {ArgumentParser<Args>::parse(arguments[index])...};
}
};
class CommandLinker
{
public:
/**
* Links a function and its arguments together into a single callable object.
*/
template<class... Args>
static std::function<void()> link(const std::function<void(Args...)>& function, const std::vector<std::string>& arguments)
{
// Parse argument vectors and store in a tuple
auto parsedArguments = ArgumentVectorParser::parse<Args...>(arguments);
// Return callable object which will invoke the function with the parsed arguments
return std::bind
(
[function, parsedArguments]()
{
std::apply(function, parsedArguments);
}
);
}
};
/**
*
*/
class CommandInterpreter
{
public:
/**
* Registers a command.
*
* @param name Name used to invoke the command.
* @param function Function associated with the command.
*/
template <class... Args>
void registerCommand(const std::string& name, const std::function<void(Args...)>& function);
template <class... Args>
void registerCommand(const std::string& name, void(*function)(Args...));
/**
* Interprets a line of text as a function call, returning the interpreted command name, argument vector, and callable function object.
*
* @param line Line of text to be interpreted.
*/
std::tuple<std::string, std::vector<std::string>, std::function<void()>> interpret(const std::string& line);
private:
template <class Function, class Linker>
void addCommandLinker(const std::string& name, const Function& function, const Linker& linker);
// A command name-keyed map of command linkers
std::unordered_map<std::string, std::function<std::function<void()>(const std::vector<std::string>&)>> linkers;
};
template <class... Args>
void CommandInterpreter::registerCommand(const std::string& name, const std::function<void(Args...)>& function)
{
addCommandLinker(name, function, CommandLinker::link<Args...>);
}
template <class... Args>
void CommandInterpreter::registerCommand(const std::string& name, void(*function)(Args...))
{
addCommandLinker(name, function, CommandLinker::link<Args...>);
}
template <class Function, class Linker>
void CommandInterpreter::addCommandLinker(const std::string& name, const Function& function, const Linker& linker)
{
linkers[name] = std::bind(linker, function, std::placeholders::_1);
}
#endif // CONSOLE_HPP

+ 95
- 29
src/game.cpp View File

@ -54,6 +54,7 @@
#include "entity/systems/steering-system.hpp"
#include "entity/systems/particle-system.hpp"
#include "entity/systems/terrain-system.hpp"
#include "configuration.hpp"
#include "stb/stb_image_write.h"
#include "menu.hpp"
#include <algorithm>
@ -63,11 +64,13 @@
#include <stdexcept>
#include <thread>
#include "debug/console.hpp"
template <>
bool Game::readSetting<std::string>(const std::string& name, std::string* value) const
{
auto it = settingsMap.find(name);
if (it == settingsMap.end())
auto it = settingsTableIndex.find(name);
if (it == settingsTableIndex.end())
{
return false;
}
@ -80,8 +83,8 @@ bool Game::readSetting(const std::string& name, std::string* value)
template <>
bool Game::readSetting<bool>(const std::string& name, bool* value) const
{
auto it = settingsMap.find(name);
if (it == settingsMap.end())
auto it = settingsTableIndex.find(name);
if (it == settingsTableIndex.end())
{
return false;
}
@ -104,8 +107,8 @@ bool Game::readSetting(const std::string& name, bool* value) const
template <>
bool Game::readSetting<int>(const std::string& name, int* value) const
{
auto it = settingsMap.find(name);
if (it == settingsMap.end())
auto it = settingsTableIndex.find(name);
if (it == settingsTableIndex.end())
{
return false;
}
@ -120,8 +123,8 @@ bool Game::readSetting(const std::string& name, int* value) const
template <>
bool Game::readSetting<float>(const std::string& name, float* value) const
{
auto it = settingsMap.find(name);
if (it == settingsMap.end())
auto it = settingsTableIndex.find(name);
if (it == settingsTableIndex.end())
{
return false;
}
@ -136,8 +139,8 @@ bool Game::readSetting(const std::string& name, float* value) const
template <>
bool Game::readSetting<Vector2>(const std::string& name, Vector2* value) const
{
auto it = settingsMap.find(name);
if (it == settingsMap.end())
auto it = settingsTableIndex.find(name);
if (it == settingsTableIndex.end())
{
return false;
}
@ -171,11 +174,13 @@ Game::Game(int argc, char* argv[]):
dataPath = getDataPath(applicationName) + "data/";
configPath = getConfigPath(applicationName);
controlsPath = configPath + "controls/";
scriptsPath = configPath + "scripts/";
// Create nonexistent config directories
std::vector<std::string> configPaths;
configPaths.push_back(configPath);
configPaths.push_back(controlsPath);
configPaths.push_back(scriptsPath);
for (const std::string& path: configPaths)
{
if (!pathExists(path))
@ -191,13 +196,11 @@ Game::Game(int argc, char* argv[]):
std::cout.rdbuf(logFileStream.rdbuf());
#endif
std::cout << "Data path: " << dataPath << std::endl;
std::cout << "Config path: " << configPath << std::endl;
// Setup resource manager
resourceManager = new ResourceManager();
// Include resource search paths in order of priority
resourceManager->include(scriptsPath);
resourceManager->include(controlsPath);
resourceManager->include(configPath);
resourceManager->include(dataPath);
@ -207,6 +210,25 @@ Game::Game(int argc, char* argv[]):
toggleFullscreenDisabled = false;
sandboxState = new SandboxState(this);
cli = new CommandInterpreter();
std::function<void()> exitCommand = std::bind(std::exit, EXIT_SUCCESS);
std::function<void(int, float, float, float)> setScaleCommand = [this](int id, float x, float y, float z) {
setScale(id, {x, y, z});
};
std::function<void()> toggleWireframeCommand = std::bind(&Game::toggleWireframe, this);
std::function<void(std::string)> shCommand = std::bind(&Game::executeShellScript, this, std::placeholders::_1);
cli->registerCommand("q", exitCommand);
cli->registerCommand("setScale", setScaleCommand);
cli->registerCommand("wireframe", toggleWireframeCommand);
cli->registerCommand("sh", shCommand);
// Start CLI thread
std::thread cliThread(&Game::interpretCommands, this);
cliThread.detach();
}
Game::~Game()
@ -235,8 +257,8 @@ std::string Game::getString(const std::string& name) const
{
std::string value;
auto it = stringMap.find(name);
if (it != stringMap.end())
auto it = stringTableIndex.find(name);
if (it != stringTableIndex.end())
{
value = (*stringTable)[it->second][languageIndex + 2];
if (value.empty())
@ -1771,7 +1793,6 @@ void Game::setupControls()
controls.addControl(&changeToolControl);
controls.addControl(&useToolControl);
controls.addControl(&toggleEditModeControl);
controls.addControl(&toggleWireframeControl);
// Build the system control set
systemControls.addControl(&exitControl);
@ -1815,7 +1836,6 @@ void Game::setupControls()
exitControl.setActivatedCallback(std::bind(&Application::close, this, EXIT_SUCCESS));
toggleFullscreenControl.setActivatedCallback(std::bind(&Game::toggleFullscreen, this));
takeScreenshotControl.setActivatedCallback(std::bind(&Game::queueScreenshot, this));
toggleWireframeControl.setActivatedCallback(std::bind(&Game::toggleWireframe, this));
// Build map of control names
controlNameMap["exit"] = &exitControl;
@ -1841,7 +1861,6 @@ void Game::setupControls()
controlNameMap["change-tool"] = &changeToolControl;
controlNameMap["use-tool"] = &useToolControl;
controlNameMap["toggle-edit-mode"] = &toggleEditModeControl;
controlNameMap["toggle-wireframe"] = &toggleWireframeControl;
// Load control profile
if (pathExists(controlsPath + controlProfileName + ".csv"))
@ -1921,12 +1940,8 @@ void Game::loadSettings()
settingsTable = new StringTable();
}
// Build settings map
for (std::size_t i = 0; i < settingsTable->size(); ++i)
{
const StringTableRow& row = (*settingsTable)[i];
settingsMap[row[0]] = i;
}
// Build settings table index
settingsTableIndex = createIndex(*settingsTable);
// Read settings from table
readSetting("language", &language);
@ -1947,11 +1962,8 @@ void Game::loadStrings()
// Read strings file
stringTable = resourceManager->load<StringTable>("strings.csv");
// Build string map
for (int row = 0; row < stringTable->size(); ++row)
{
stringMap[(*stringTable)[row][0]] = row;
}
// Build string table index
stringTableIndex = createIndex(*stringTable);
}
void Game::loadFonts()
@ -3075,6 +3087,7 @@ void Game::enterTitleState()
// Setup scene
Vector3 antHillTranslation = {0, 0, 0};
EntityID antHill = createInstanceOf("ant-hill");
std::cout << antHill << std::endl;
setTranslation(antHill, antHillTranslation);
// Setup camera
@ -3234,6 +3247,28 @@ void Game::returnToMainMenu()
fadeOut(3.0f, Vector3(0.0f), std::bind(&StateMachine::changeState, this, &titleState));
}
void Game::interpretCommands()
{
std::cout << "Antkeeper " << VERSION_STRING << std::endl;
while (1)
{
std::cout << "> " << std::flush;
std::string line;
std::getline(std::cin, line);
auto [name, arguments, call] = cli->interpret(line);
if (call)
{
call();
}
else
{
std::cout << "ant: Unknown command " << name << std::endl;
}
}
}
void Game::boxSelect(float x, float y, float w, float h)
{
boxSelectionContainer->setTranslation(Vector2(x, y));
@ -3415,6 +3450,37 @@ void Game::setTerrainPatchPosition(EntityID entity, const std::tuple&
component->position = position;
}
void Game::executeShellScript(const std::string& string)
{
TextFile* script = nullptr;
try
{
script = resourceManager->load<TextFile>(string);
}
catch (const std::exception& e)
{
std::cerr << "Failed to load shell script: \"" << e.what() << "\"" << std::endl;
return;
}
for (const std::string& line: *script)
{
if (!line.empty())
{
auto [name, arguments, call] = cli->interpret(line);
if (call)
{
call();
}
else
{
std::cout << "ant: Unknown command " << name << std::endl;
}
}
}
}
void Game::saveScreenshot(const std::string& filename, unsigned int width, unsigned int height, unsigned char* pixels)
{
stbi_flip_vertically_on_write(1);

+ 9
- 7
src/game.hpp View File

@ -26,6 +26,7 @@ using namespace Emergent;
#include "state-machine.hpp"
#include "entity/entity-id.hpp"
#include "scheduled-function-event.hpp"
#include "resources/string-table.hpp"
#include <array>
#include <map>
#include <string>
@ -70,8 +71,8 @@ class TerrainSystem;
class ComponentBase;
class Menu;
class MenuItem;
class CommandInterpreter;
enum class ComponentType;
typedef std::vector<std::vector<std::string>> StringTable;
class Game:
public Application,
@ -206,6 +207,8 @@ private:
void newGame();
void returnToMainMenu();
void interpretCommands();
public:
EntityID createInstance();
EntityID createInstanceOf(const std::string& templateName);
@ -216,6 +219,7 @@ public:
void setRotation(EntityID entity, const Quaternion& rotation);
void setScale(EntityID entity, const Vector3& scale);
void setTerrainPatchPosition(EntityID entity, const std::tuple<int, int>& position);
void executeShellScript(const std::string& string);
void boxSelect(float x, float y, float w, float h);
@ -237,14 +241,15 @@ public:
std::string dataPath;
std::string configPath;
std::string controlsPath;
std::string scriptsPath;
// Settings
StringTable* settingsTable;
std::map<std::string, std::size_t> settingsMap;
StringTableIndex settingsTableIndex;
// Localization
StringTable* stringTable;
std::map<std::string, std::size_t> stringMap;
StringTableIndex stringTableIndex;
std::size_t languageCount;
std::size_t languageIndex;
@ -299,10 +304,6 @@ public:
ControlSet editorControls;
Control toggleEditModeControl;
// Debug control set
ControlSet debugControls;
Control toggleWireframeControl;
// Map of control names
std::map<std::string, Control*> controlNameMap;
@ -539,6 +540,7 @@ public:
bool toggleFullscreenDisabled;
// Debugging
CommandInterpreter* cli;
std::ofstream logFileStream;
bool wireframe;

+ 0
- 3
src/game/forceps.cpp View File

@ -52,9 +52,6 @@ Forceps::Forceps(const Model* model, Animator* animator):
float pinchSpeed = std::get<1>(pinchClip->getTimeFrame()) / pinchDuration;
float releaseSpeed = std::get<1>(releaseClip->getTimeFrame()) / releaseDuration;
std::cout << std::get<1>(pinchClip->getTimeFrame()) << std::endl;
std::cout << std::get<1>(releaseClip->getTimeFrame()) << std::endl;
// Setup pinch animation callbacks
pinchAnimation.setSpeed(pinchSpeed);
pinchAnimation.setTimeFrame(pinchClip->getTimeFrame());

+ 12
- 2
src/resources/string-table.hpp View File

@ -20,14 +20,24 @@
#ifndef STRING_TABLE_HPP
#define STRING_TABLE_HPP
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
/**
* A single row in a string table.
*/
typedef std::vector<std::string> StringTableRow;
/**
* A table of strings.
*/
typedef std::vector<StringTableRow> StringTable;
typedef std::map<std::string, std::size_t> StringTableIndex;
/**
* An index for finding elements in a string table.
*/
typedef std::unordered_map<std::string, std::size_t> StringTableIndex;
/**
* Creates an index for a string table using strings in the first column as keys.

Loading…
Cancel
Save