diff --git a/src/debug/console.cpp b/src/debug/console.cpp new file mode 100644 index 0000000..7e6e86f --- /dev/null +++ b/src/debug/console.cpp @@ -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 . + */ + +#include "console.hpp" +#include + +template<> +int ArgumentParser::parse(const std::string& argument) +{ + return std::stoi(argument); +} + +template<> +unsigned int ArgumentParser::parse(const std::string& argument) +{ + return static_cast(std::stoul(argument)); +} + +template<> +long ArgumentParser::parse(const std::string& argument) +{ + return std::stol(argument); +} + +template<> +unsigned long ArgumentParser::parse(const std::string& argument) +{ + return std::stoul(argument); +} + +template<> +float ArgumentParser::parse(const std::string& argument) +{ + return std::stof(argument); +} + +template<> +double ArgumentParser::parse(const std::string& argument) +{ + return std::stod(argument); +} + +template<> +std::string ArgumentParser::parse(const std::string& argument) +{ + return argument; +} + +std::tuple, std::function> CommandInterpreter::interpret(const std::string& line) +{ + // Split line into arguments + std::vector 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(), 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 call = linker->second(arguments); + + return {name, arguments, call}; +} + diff --git a/src/debug/console.hpp b/src/debug/console.hpp new file mode 100644 index 0000000..a00d9ac --- /dev/null +++ b/src/debug/console.hpp @@ -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 . + */ + +#ifndef CONSOLE_HPP +#define CONSOLE_HPP + +#include +#include +#include +#include +#include + +/** + * Parses an argument string into a typed value. + */ +template +class ArgumentParser +{ +public: + static T parse(const std::string& argument); +}; + +template<> +int ArgumentParser::parse(const std::string& argument); +template<> +unsigned int ArgumentParser::parse(const std::string& argument); +template<> +long ArgumentParser::parse(const std::string& argument); +template<> +unsigned long ArgumentParser::parse(const std::string& argument); +template<> +float ArgumentParser::parse(const std::string& argument); +template<> +double ArgumentParser::parse(const std::string& argument); +template<> +std::string ArgumentParser::parse(const std::string& argument); + +/** + * Parses an argument vector of strings into a tuple of typed values. + */ +class ArgumentVectorParser +{ +public: + template + static std::tuple parse(const std::vector& arguments) + { + return parse(arguments, std::make_index_sequence{}); + } + +private: + template + static std::tuple parse(const std::vector& arguments, std::index_sequence) + { + return {ArgumentParser::parse(arguments[index])...}; + } +}; + +class CommandLinker +{ +public: + /** + * Links a function and its arguments together into a single callable object. + */ + template + static std::function link(const std::function& function, const std::vector& arguments) + { + // Parse argument vectors and store in a tuple + auto parsedArguments = ArgumentVectorParser::parse(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 + void registerCommand(const std::string& name, const std::function& function); + template + 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::function> interpret(const std::string& line); + +private: + template + void addCommandLinker(const std::string& name, const Function& function, const Linker& linker); + + // A command name-keyed map of command linkers + std::unordered_map(const std::vector&)>> linkers; +}; + +template +void CommandInterpreter::registerCommand(const std::string& name, const std::function& function) +{ + addCommandLinker(name, function, CommandLinker::link); +} + +template +void CommandInterpreter::registerCommand(const std::string& name, void(*function)(Args...)) +{ + addCommandLinker(name, function, CommandLinker::link); +} + +template +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 + diff --git a/src/game.cpp b/src/game.cpp index d8b2760..045d3cb 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -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 @@ -63,11 +64,13 @@ #include #include +#include "debug/console.hpp" + template <> bool Game::readSetting(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(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(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(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(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 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 exitCommand = std::bind(std::exit, EXIT_SUCCESS); + std::function setScaleCommand = [this](int id, float x, float y, float z) { + setScale(id, {x, y, z}); + }; + std::function toggleWireframeCommand = std::bind(&Game::toggleWireframe, this); + std::function 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("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(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); diff --git a/src/game.hpp b/src/game.hpp index 69acf98..7d47742 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -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 #include #include @@ -70,8 +71,8 @@ class TerrainSystem; class ComponentBase; class Menu; class MenuItem; +class CommandInterpreter; enum class ComponentType; -typedef std::vector> 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& 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 settingsMap; + StringTableIndex settingsTableIndex; // Localization StringTable* stringTable; - std::map 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 controlNameMap; @@ -539,6 +540,7 @@ public: bool toggleFullscreenDisabled; // Debugging + CommandInterpreter* cli; std::ofstream logFileStream; bool wireframe; diff --git a/src/game/forceps.cpp b/src/game/forceps.cpp index 0df4cac..436cc4e 100644 --- a/src/game/forceps.cpp +++ b/src/game/forceps.cpp @@ -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()); diff --git a/src/resources/string-table.hpp b/src/resources/string-table.hpp index 9961151..e857712 100644 --- a/src/resources/string-table.hpp +++ b/src/resources/string-table.hpp @@ -20,14 +20,24 @@ #ifndef STRING_TABLE_HPP #define STRING_TABLE_HPP -#include #include +#include #include +/** + * A single row in a string table. + */ typedef std::vector StringTableRow; + +/** + * A table of strings. + */ typedef std::vector StringTable; -typedef std::map StringTableIndex; +/** + * An index for finding elements in a string table. + */ +typedef std::unordered_map StringTableIndex; /** * Creates an index for a string table using strings in the first column as keys.