From 7cc22274dfd33946315229db03ec316d0276d0e6 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Fri, 9 Dec 2022 19:47:26 +0100 Subject: [PATCH] Added GenericDocument + Reader API to AngelScript New script API: * class GenericDocumentClass - loads/saves a document from OGRE resource system * class GenericDocReaderClass - traverses document tokens * enum TokenType (TOKEN_TYPE_*) * enum GenericDocumentOptions (GENERIC_DOCUMENT_OPTION_*) * function ImGui::AlignTextToFramePadding() New features of demo_script.as: * a "View document" button next to the vehicle name - it will tokenize the truck definition file and open a separate window with syntax-highlighted file contents. Known issues: * DearIMGUI windows opened by script can't be closed with X button. This is a global flaw in our DearIMGUI integration * GenericDocument doesn't understand the truck title (first nonempty noncomment line). * There are glitches in parsing naked strings - an extra bool is emitted instead. * There are glitches in parsing keywords - a string is emitted instead. Codechanges: * GenericFileFormat: added RefCountingObject logic. Renamed Document to GenericDocument, added funcs {LoadFrom/SaveTo}Resource(). Renamed Reader to GenericDocReader, GetArg*() funcs renamed to GetTok*() - the Arg naming was to match existing parsers which is now moot. Added {Get/Is}TokComment(). Renamed GetType() to GetTokType(). * Actor + ActorAngelscript: added func getTruckFileResourceGroup() - name chosen to match existing getTruckFileName(). * ImGuiAngelscript - added binding of ImGui::AlignTextToFramePadding() --- resources/scripts/demo_script.as | 95 ++++++++++++++- source/main/CMakeLists.txt | 1 + source/main/physics/Actor.cpp | 5 + source/main/physics/Actor.h | 1 + source/main/scripting/ScriptEngine.cpp | 1 + .../scripting/bindings/ActorAngelscript.cpp | 1 + .../scripting/bindings/AngelScriptBindings.h | 3 + .../bindings/GenericFileFormatAngelscript.cpp | 113 ++++++++++++++++++ .../scripting/bindings/ImGuiAngelscript.cpp | 2 +- source/main/utils/GenericFileFormat.cpp | 73 +++++++---- source/main/utils/GenericFileFormat.h | 64 +++++----- 11 files changed, 304 insertions(+), 55 deletions(-) create mode 100644 source/main/scripting/bindings/GenericFileFormatAngelscript.cpp diff --git a/resources/scripts/demo_script.as b/resources/scripts/demo_script.as index b1f2e6fa8f..b331c04b01 100644 --- a/resources/scripts/demo_script.as +++ b/resources/scripts/demo_script.as @@ -9,6 +9,7 @@ * Collect and show stats (i.e. frame count, total time) * Read/Write cvars (RoR.cfg values, cli args, game state...) * View and update game state (current vehicle...) + * Parse and display definition files with syntax highlighting. There are 3 ways of invoking a script: 1. By defining it with terrain, see terrn2 fileformat, section '[Scripts]': @@ -21,13 +22,13 @@ For introduction to game events, read https://docs.rigsofrods.org/terrain-creation/scripting/. - - For reference manual of the interface, see - https://github.com/RigsOfRods/rigs-of-rods/tree/master/doc/angelscript. + + Scripting documentation: + https://developer.rigsofrods.org/d4/d07/group___script2_game.html + --------------------------------------------------------------------------- */ - /* --------------------------------------------------------------------------- Global variables @@ -38,6 +39,7 @@ CVarClass@ g_app_state = console.cVarFind("app_state"); // 0=bootstrap, 1=main CVarClass@ g_sim_state = console.cVarFind("sim_state"); // 0=off, 1=running, 2=paused, 3=terrain editor, see SimState in Application.h CVarClass@ g_mp_state = console.cVarFind("mp_state"); // 0=disabled, 1=connecting, 2=connected, see MpState in Application.h CVarClass@ g_io_arcade_controls = console.cVarFind("io_arcade_controls"); // bool +GenericDocumentClass@ g_displayed_document = null; /* --------------------------------------------------------------------------- @@ -100,9 +102,31 @@ void frameStep(float dt) ImGui::Text("Toggle fixed camera: " + inputs.getEventCommandTrimmed(EV_CAMERA_FREE_MODE_FIX)); BeamClass@ actor = game.getCurrentTruck(); - if (actor != null) + if (@actor != null) { + ImGui::AlignTextToFramePadding(); ImGui::Text("You are driving " + actor.getTruckName()); + ImGui::SameLine(); + if (@g_displayed_document == null) + { + if (ImGui::Button("View document")) + { + GenericDocumentClass@ doc = GenericDocumentClass(); + int flags = GENERIC_DOCUMENT_OPTION_ALLOW_NAKED_STRINGS | GENERIC_DOCUMENT_OPTION_ALLOW_SLASH_COMMENTS; + if (doc.LoadFromResource(actor.getTruckFileName(), actor.getTruckFileResourceGroup(), flags)) + { + @g_displayed_document = @doc; + } + } + } + else + { + if (ImGui::Button("Close document")) + { + @g_displayed_document = null; + } + } + ImGui::TextDisabled("Vehicle controls:"); ImGui::Text("Accelerate/Brake: " @@ -157,4 +181,63 @@ void frameStep(float dt) // Update global counters g_total_frames++; g_total_seconds += dt; -} \ No newline at end of file + + // Draw document window + if (@g_displayed_document != null) + { + drawDocumentWindow(); + } +} + +void drawDocumentWindow() +{ + ImGui::PushID("document view"); + ImGui::Begin("Document view", /*open:*/true, /*flags:*/0); + + GenericDocReaderClass reader(g_displayed_document); + while (!reader.EndOfFile()) + { + switch (reader.GetTokType()) + { + // These tokens are always at start of line + case TOKEN_TYPE_KEYWORD: + ImGui::TextColored(color(1.f, 1.f, 0.f, 1.f), reader.GetTokKeyword()); + break; + case TOKEN_TYPE_COMMENT: + ImGui::TextDisabled(";" + reader.GetTokComment()); + break; + + // Linebreak is implicit in DearIMGUI, no action needed + case TOKEN_TYPE_LINEBREAK: + break; + + // Other tokens come anywhere - delimiting logic is needed + default: + if (reader.GetTokType(-1) != TOKEN_TYPE_LINEBREAK) + { + ImGui::SameLine(); + string delimiter = (reader.GetTokType(-1) == TOKEN_TYPE_KEYWORD) ? " " : ", "; + ImGui::Text(delimiter); + ImGui::SameLine(); + } + + switch (reader.GetTokType()) + { + case TOKEN_TYPE_STRING: + ImGui::TextColored(color(0.f, 1.f, 1.f, 1.f), "\"" + reader.GetTokString() + "\""); + break; + case TOKEN_TYPE_NUMBER: + ImGui::Text("" + reader.GetTokFloat()); + break; + case TOKEN_TYPE_BOOL: + ImGui::TextColored(color(1.f, 0.f, 1.f, 1.f), ""+reader.GetTokBool()); + break; + } + } + + reader.MoveNext(); + } + + ImGui::End(); + ImGui::PopID(); //"document view" +} diff --git a/source/main/CMakeLists.txt b/source/main/CMakeLists.txt index 84633fb562..765a03b3a7 100644 --- a/source/main/CMakeLists.txt +++ b/source/main/CMakeLists.txt @@ -230,6 +230,7 @@ if (USE_ANGELSCRIPT) scripting/bindings/ActorAngelscript.cpp scripting/bindings/ConsoleAngelscript.cpp scripting/bindings/GameScriptAngelscript.cpp + scripting/bindings/GenericFileFormatAngelscript.cpp scripting/bindings/ImGuiAngelscript.cpp scripting/bindings/InputEngineAngelscript.cpp scripting/bindings/LocalStorageAngelscript.cpp diff --git a/source/main/physics/Actor.cpp b/source/main/physics/Actor.cpp index 14fe71828b..7133e3ff6f 100644 --- a/source/main/physics/Actor.cpp +++ b/source/main/physics/Actor.cpp @@ -4650,3 +4650,8 @@ void Actor::UpdatePropAnimInputEvents() state.event_active_prev = ev_active; } } + +std::string Actor::getTruckFileResourceGroup() +{ + return m_gfx_actor->GetResourceGroup(); +} diff --git a/source/main/physics/Actor.h b/source/main/physics/Actor.h index 800699d1c7..379c360453 100644 --- a/source/main/physics/Actor.h +++ b/source/main/physics/Actor.h @@ -178,6 +178,7 @@ class Actor : public ZeroedMemoryAllocator /// @{ std::string getTruckName() { return ar_design_name; } std::string getTruckFileName() { return ar_filename; } + std::string getTruckFileResourceGroup(); int getTruckType() { return ar_driveable; } Ogre::String getSectionConfig() { return m_section_config; } CacheEntry* getUsedSkin() { return m_used_skin_entry; } diff --git a/source/main/scripting/ScriptEngine.cpp b/source/main/scripting/ScriptEngine.cpp index 717890a6e6..49f91e3200 100644 --- a/source/main/scripting/ScriptEngine.cpp +++ b/source/main/scripting/ScriptEngine.cpp @@ -163,6 +163,7 @@ void ScriptEngine::init() RegisterTerrain(engine); // TerrainClass RegisterGameScript(engine); // GameScriptClass RegisterScriptEvents(engine); // scriptEvents + RegisterGenericFileFormat(engine); // TokenType, GenericDocumentClass, GenericDocReaderClass // now the global instances result = engine->RegisterGlobalProperty("GameScriptClass game", &m_game_script); ROR_ASSERT(result>=0); diff --git a/source/main/scripting/bindings/ActorAngelscript.cpp b/source/main/scripting/bindings/ActorAngelscript.cpp index 71eb71586e..46a333c389 100644 --- a/source/main/scripting/bindings/ActorAngelscript.cpp +++ b/source/main/scripting/bindings/ActorAngelscript.cpp @@ -51,6 +51,7 @@ void RoR::RegisterActor(asIScriptEngine *engine) result = engine->RegisterObjectMethod("BeamClass", "void scaleTruck(float)", asMETHOD(Actor,scaleTruck), asCALL_THISCALL); ROR_ASSERT(result>=0); result = engine->RegisterObjectMethod("BeamClass", "string getTruckName()", asMETHOD(Actor,getTruckName), asCALL_THISCALL); ROR_ASSERT(result>=0); result = engine->RegisterObjectMethod("BeamClass", "string getTruckFileName()", asMETHOD(Actor,getTruckFileName), asCALL_THISCALL); ROR_ASSERT(result>=0); + result = engine->RegisterObjectMethod("BeamClass", "string getTruckFileResourceGroup()", asMETHOD(Actor, getTruckFileResourceGroup), asCALL_THISCALL); ROR_ASSERT(result >= 0); result = engine->RegisterObjectMethod("BeamClass", "string getSectionConfig()", asMETHOD(Actor, getSectionConfig), asCALL_THISCALL); ROR_ASSERT(result >= 0); result = engine->RegisterObjectMethod("BeamClass", "int getTruckType()", asMETHOD(Actor,getTruckType), asCALL_THISCALL); ROR_ASSERT(result>=0); result = engine->RegisterObjectMethod("BeamClass", "void reset(bool)", asMETHOD(Actor,reset), asCALL_THISCALL); ROR_ASSERT(result>=0); diff --git a/source/main/scripting/bindings/AngelScriptBindings.h b/source/main/scripting/bindings/AngelScriptBindings.h index b9aa54407e..fc0ddc0ccb 100644 --- a/source/main/scripting/bindings/AngelScriptBindings.h +++ b/source/main/scripting/bindings/AngelScriptBindings.h @@ -70,6 +70,9 @@ void RegisterTerrain(AngelScript::asIScriptEngine* engine); /// defined in ProceduralRoadAngelscript.cpp void RegisterProceduralRoad(AngelScript::asIScriptEngine* engine); +/// defined in GenericFileFormatAngelscript.cpp +void RegisterGenericFileFormat(AngelScript::asIScriptEngine* engine); + /// @} //addtogroup Scripting diff --git a/source/main/scripting/bindings/GenericFileFormatAngelscript.cpp b/source/main/scripting/bindings/GenericFileFormatAngelscript.cpp new file mode 100644 index 0000000000..d32670da0e --- /dev/null +++ b/source/main/scripting/bindings/GenericFileFormatAngelscript.cpp @@ -0,0 +1,113 @@ +/* + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2022 Petr Ohlidal + + For more information, see http://www.rigsofrods.org/ + + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. + + Rigs of Rods 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 Rigs of Rods. If not, see . +*/ + +/// @file +/// @author Petr Ohlidal +/// @date 12-2022 + +#include "AngelScriptBindings.h" +#include "GenericFileFormat.h" + +using namespace RoR; +using namespace AngelScript; + +// Wrappers +static std::string GenericDocReader_GetTokString(GenericDocReader* reader, uint32_t offset) +{ + const char* val = reader->GetTokString(offset); + return (val) ? val : ""; +} + +static std::string GenericDocReader_GetTokKeyword(GenericDocReader* reader, uint32_t offset) +{ + const char* val = reader->GetTokKeyword(offset); + return (val) ? val : ""; +} + +static std::string GenericDocReader_GetTokComment(GenericDocReader* reader, uint32_t offset) +{ + const char* val = reader->GetTokComment(offset); + return (val) ? val : ""; +} + +// Factories +static GenericDocument* GenericDocumentFactory() +{ + return new GenericDocument(); +} + +static GenericDocReader* GenericDocReaderFactory(GenericDocumentPtr doc) +{ + return new GenericDocReader(doc); +} + +void RoR::RegisterGenericFileFormat(asIScriptEngine* engine) +{ + // enum TokenType + engine->RegisterEnum("TokenType"); + engine->RegisterEnumValue("TokenType", "TOKEN_TYPE_NONE", (int)TokenType::NONE); + engine->RegisterEnumValue("TokenType", "TOKEN_TYPE_LINEBREAK", (int)TokenType::LINEBREAK); + engine->RegisterEnumValue("TokenType", "TOKEN_TYPE_COMMENT", (int)TokenType::COMMENT); + engine->RegisterEnumValue("TokenType", "TOKEN_TYPE_STRING", (int)TokenType::STRING); + engine->RegisterEnumValue("TokenType", "TOKEN_TYPE_NUMBER", (int)TokenType::NUMBER); + engine->RegisterEnumValue("TokenType", "TOKEN_TYPE_BOOL", (int)TokenType::BOOL); + engine->RegisterEnumValue("TokenType", "TOKEN_TYPE_KEYWORD", (int)TokenType::KEYWORD); + + + // GenericDocument constants + engine->RegisterEnum("GenericDocumentOptions"); + engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_ALLOW_NAKED_STRINGS", GenericDocument::OPTION_ALLOW_NAKED_STRINGS); + engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_ALLOW_SLASH_COMMENTS", GenericDocument::OPTION_ALLOW_SLASH_COMMENTS); + + + // class GenericDocument + GenericDocument::RegisterRefCountingObject(engine, "GenericDocumentClass"); + GenericDocumentPtr::RegisterRefCountingObjectPtr(engine, "GenericDocumentClassPtr", "GenericDocumentClass"); + engine->RegisterObjectBehaviour("GenericDocumentClass", asBEHAVE_FACTORY, "GenericDocumentClass@ f()", asFUNCTION(GenericDocumentFactory), asCALL_CDECL); + + engine->RegisterObjectMethod("GenericDocumentClass", "bool LoadFromResource(string,string,int)", asMETHOD(GenericDocument, LoadFromResource), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocumentClass", "bool SaveToResource(string,string)", asMETHOD(GenericDocument, SaveToResource), asCALL_THISCALL); + + + // class GenericDocReader + GenericDocReader::RegisterRefCountingObject(engine, "GenericDocReaderClass"); + GenericDocReaderPtr::RegisterRefCountingObjectPtr(engine, "GenericDocReaderClassPtr", "GenericDocReaderClass"); + engine->RegisterObjectBehaviour("GenericDocReaderClass", asBEHAVE_FACTORY, "GenericDocReaderClass@ f(GenericDocumentClassPtr @)", asFUNCTION(GenericDocReaderFactory), asCALL_CDECL); + + engine->RegisterObjectMethod("GenericDocReaderClass", "bool MoveNext()", asMETHOD(GenericDocReader, MoveNext), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "bool SeekNextLine()", asMETHOD(GenericDocReader, SeekNextLine), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "uint CountLineArgs()", asMETHOD(GenericDocReader, CountLineArgs), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "bool EndOfFile(int offset = 0)", asMETHOD(GenericDocReader, EndOfFile), asCALL_THISCALL); + + engine->RegisterObjectMethod("GenericDocReaderClass", "TokenType GetTokType(int offset = 0)", asMETHOD(GenericDocReader, GetTokType), asCALL_THISCALL); + + engine->RegisterObjectMethod("GenericDocReaderClass", "string GetTokString(int offset = 0)", asFUNCTION(GenericDocReader_GetTokString), asCALL_CDECL_OBJFIRST); + engine->RegisterObjectMethod("GenericDocReaderClass", "float GetTokFloat(int offset = 0)", asMETHOD(GenericDocReader, GetTokFloat), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "bool GetTokBool(int offset = 0)", asMETHOD(GenericDocReader, GetTokBool), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "string GetTokKeyword(int offset = 0)", asFUNCTION(GenericDocReader_GetTokKeyword), asCALL_CDECL_OBJFIRST); + engine->RegisterObjectMethod("GenericDocReaderClass", "string GetTokComment(int offset = 0)", asFUNCTION(GenericDocReader_GetTokComment), asCALL_CDECL_OBJFIRST); + + engine->RegisterObjectMethod("GenericDocReaderClass", "bool IsTokString(int offset = 0)", asMETHOD(GenericDocReader, IsTokString), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "bool IsTokFloat(int offset = 0)", asMETHOD(GenericDocReader, IsTokFloat), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "bool IsTokBool(int offset = 0)", asMETHOD(GenericDocReader, IsTokBool), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "bool IsTokKeyword(int offset = 0)", asMETHOD(GenericDocReader, IsTokKeyword), asCALL_THISCALL); + engine->RegisterObjectMethod("GenericDocReaderClass", "bool IsTokComment(int offset = 0)", asMETHOD(GenericDocReader, IsTokComment), asCALL_THISCALL); +} diff --git a/source/main/scripting/bindings/ImGuiAngelscript.cpp b/source/main/scripting/bindings/ImGuiAngelscript.cpp index d2cb108374..6e5ce931dd 100644 --- a/source/main/scripting/bindings/ImGuiAngelscript.cpp +++ b/source/main/scripting/bindings/ImGuiAngelscript.cpp @@ -179,7 +179,7 @@ void RoR::RegisterImGuiBindings(AngelScript::asIScriptEngine* engine) engine->RegisterGlobalFunction("vector2 GetCursorStartPos()", asFUNCTIONPR([]() { auto v = ImGui::GetCursorStartPos(); return Vector2(v.x, v.y); }, (), Vector2), asCALL_CDECL); engine->RegisterGlobalFunction("vector2 GetCursorScreenPos()", asFUNCTIONPR([]() { auto v = ImGui::GetCursorScreenPos(); return Vector2(v.x, v.y); }, (), Vector2), asCALL_CDECL); engine->RegisterGlobalFunction("void SetCursorScreenPos(vector2)", asFUNCTIONPR([](Vector2 v) { ImGui::SetCursorScreenPos(ImVec2(v.x, v.y)); }, (Vector2), void), asCALL_CDECL); - // engine->RegisterGlobalFunction("void AlignTextToFramePadding()", asFUNCTIONPR(ImGui::AlignTextToFramePadding, (), void), asCALL_CDECL); + engine->RegisterGlobalFunction("void AlignTextToFramePadding()", asFUNCTIONPR(ImGui::AlignTextToFramePadding, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("float GetTextLineHeight()", asFUNCTIONPR(ImGui::GetTextLineHeight, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetTextLineHeightWithSpacing()", asFUNCTIONPR(ImGui::GetTextLineHeightWithSpacing, (), float), asCALL_CDECL); // engine->RegisterGlobalFunction("float GetFrameHeight()", asFUNCTIONPR(ImGui::GetFrameHeight, (), float), asCALL_CDECL); diff --git a/source/main/utils/GenericFileFormat.cpp b/source/main/utils/GenericFileFormat.cpp index c57241f443..9e70800416 100644 --- a/source/main/utils/GenericFileFormat.cpp +++ b/source/main/utils/GenericFileFormat.cpp @@ -44,11 +44,11 @@ enum class PartialToken struct DocumentParser { - DocumentParser(Document& d, const BitMask_t opt, Ogre::DataStreamPtr ds) + DocumentParser(GenericDocument& d, const BitMask_t opt, Ogre::DataStreamPtr ds) : doc(d), options(opt), datastream(ds) {} // Config - Document& doc; + GenericDocument& doc; const BitMask_t options; Ogre::DataStreamPtr datastream; @@ -92,11 +92,11 @@ void DocumentParser::BeginToken(const char c) break; case '/': - if (options & Document::OPTION_ALLOW_SLASH_COMMENTS) + if (options & GenericDocument::OPTION_ALLOW_SLASH_COMMENTS) { partial_tok_type = PartialToken::COMMENT_SLASH; } - else if (options & Document::OPTION_ALLOW_NAKED_STRINGS && + else if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS && (doc.tokens.size() != 0 && doc.tokens.back().type != TokenType::LINEBREAK)) // not first on line? { tok.push_back(c); @@ -151,7 +151,7 @@ void DocumentParser::BeginToken(const char c) tok.push_back(c); partial_tok_type = PartialToken::KEYWORD; } - else if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + else if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) { tok.push_back(c); partial_tok_type = PartialToken::STRING_NAKED; @@ -375,7 +375,7 @@ void DocumentParser::UpdateBool(const char c) case 'r': if (partial_tok_type != PartialToken::BOOL_TRUE || tok.size() != 1) { - if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) partial_tok_type = PartialToken::STRING_NAKED; else partial_tok_type = PartialToken::GARBAGE; @@ -387,7 +387,7 @@ void DocumentParser::UpdateBool(const char c) case 'u': if (partial_tok_type != PartialToken::BOOL_TRUE || tok.size() != 2) { - if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) partial_tok_type = PartialToken::STRING_NAKED; else partial_tok_type = PartialToken::GARBAGE; @@ -399,7 +399,7 @@ void DocumentParser::UpdateBool(const char c) case 'a': if (partial_tok_type != PartialToken::BOOL_FALSE || tok.size() != 1) { - if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) partial_tok_type = PartialToken::STRING_NAKED; else partial_tok_type = PartialToken::GARBAGE; @@ -411,7 +411,7 @@ void DocumentParser::UpdateBool(const char c) case 'l': if (partial_tok_type != PartialToken::BOOL_FALSE || tok.size() != 2) { - if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) partial_tok_type = PartialToken::STRING_NAKED; else partial_tok_type = PartialToken::GARBAGE; @@ -423,7 +423,7 @@ void DocumentParser::UpdateBool(const char c) case 's': if (partial_tok_type != PartialToken::BOOL_FALSE || tok.size() != 3) { - if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) partial_tok_type = PartialToken::STRING_NAKED; else partial_tok_type = PartialToken::GARBAGE; @@ -447,7 +447,7 @@ void DocumentParser::UpdateBool(const char c) } else { - if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) partial_tok_type = PartialToken::STRING_NAKED; else partial_tok_type = PartialToken::GARBAGE; @@ -457,7 +457,7 @@ void DocumentParser::UpdateBool(const char c) break; default: - if (options & Document::OPTION_ALLOW_NAKED_STRINGS) + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) partial_tok_type = PartialToken::STRING_NAKED; else partial_tok_type = PartialToken::GARBAGE; @@ -547,7 +547,7 @@ void DocumentParser::UpdateGarbage(const char c) } } -void Document::Load(Ogre::DataStreamPtr datastream, const BitMask_t options) +void GenericDocument::LoadFromDataStream(Ogre::DataStreamPtr datastream, const BitMask_t options) { // Reset the document tokens.clear(); @@ -616,7 +616,7 @@ void Document::Load(Ogre::DataStreamPtr datastream, const BitMask_t options) const char* EOL_STR = "\n"; // "LF" #endif -void Document::Save(Ogre::DataStreamPtr datastream) +void GenericDocument::SaveToDataStream(Ogre::DataStreamPtr datastream) { std::string separator; const char* pool_str = nullptr; @@ -668,16 +668,48 @@ void Document::Save(Ogre::DataStreamPtr datastream) } } -bool Reader::SeekNextLine() +bool GenericDocument::LoadFromResource(std::string resource_name, std::string resource_group_name, BitMask_t options/* = 0*/) +{ + try + { + Ogre::DataStreamPtr datastream = Ogre::ResourceGroupManager::getSingleton().openResource(resource_name, resource_group_name); + this->LoadFromDataStream(datastream, options); + return true; + } + catch (Ogre::Exception& eeh) + { + App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR, + fmt::format("GenericDocument: could not load file '{}' from resource group '{}': {}", resource_name, resource_group_name, eeh.getDescription())); + return false; + } +} + +bool GenericDocument::SaveToResource(std::string resource_name, std::string resource_group_name) +{ + try + { + Ogre::DataStreamPtr datastream = Ogre::ResourceGroupManager::getSingleton().createResource(resource_name, resource_group_name); + this->SaveToDataStream(datastream); + return true; + } + catch (Ogre::Exception& eeh) + { + App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR, + fmt::format("GenericDocument: could not write file '{}' to resource group '{}': {}", resource_name, resource_group_name, eeh.getDescription())); + return false; + } +} + +bool GenericDocReader::SeekNextLine() { // Skip current line - while (!this->EndOfFile() && this->GetType() != TokenType::LINEBREAK) + while (!this->EndOfFile() && this->GetTokType() != TokenType::LINEBREAK) { this->MoveNext(); } // Skip comments - while (!this->EndOfFile() && !this->IsArgString() && !this->IsArgFloat() && !this->IsArgBool() && !this->IsArgKeyword()) + while (!this->EndOfFile() && !this->IsTokString() && !this->IsTokFloat() && !this->IsTokBool() && !this->IsTokKeyword()) { this->MoveNext(); } @@ -685,11 +717,10 @@ bool Reader::SeekNextLine() return this->EndOfFile(); } -size_t Reader::CountLineArgs() +int GenericDocReader::CountLineArgs() { - size_t count = 0; - while (!EndOfFile(count) && this->GetType(count) != TokenType::LINEBREAK) + int count = 0; + while (!EndOfFile(count) && this->GetTokType(count) != TokenType::LINEBREAK) count++; return count; } - diff --git a/source/main/utils/GenericFileFormat.h b/source/main/utils/GenericFileFormat.h index 82ddcfbba5..dba2358ef4 100644 --- a/source/main/utils/GenericFileFormat.h +++ b/source/main/utils/GenericFileFormat.h @@ -31,6 +31,8 @@ /// - Strings cannot be multiline. Linebreak within string ends the string. /// - KEYWORD tokens cannot start with a digit or special character. +#include "RefCountingObject.h" + #include #include @@ -55,48 +57,56 @@ struct Token float data; }; -struct Document +struct GenericDocument: public RefCountingObject { static const BitMask_t OPTION_ALLOW_NAKED_STRINGS = BITMASK(1); //!< Allow strings without quotes, for backwards compatibility. static const BitMask_t OPTION_ALLOW_SLASH_COMMENTS = BITMASK(2); //!< Allow comments starting with `//`. - virtual ~Document() {}; + virtual ~GenericDocument() {}; std::vector string_pool; // Data of COMMENT/KEYWORD/STRING tokens; NUL-terminated strings. std::vector tokens; - virtual void Load(Ogre::DataStreamPtr datastream, BitMask_t options = 0); - virtual void Save(Ogre::DataStreamPtr datastream); + virtual void LoadFromDataStream(Ogre::DataStreamPtr datastream, BitMask_t options = 0); + virtual void SaveToDataStream(Ogre::DataStreamPtr datastream); + + virtual bool LoadFromResource(std::string resource_name, std::string resource_group_name, BitMask_t options = 0); + virtual bool SaveToResource(std::string resource_name, std::string resource_group_name); }; -struct Reader +typedef RefCountingObjectPtr GenericDocumentPtr; + +struct GenericDocReader: public RefCountingObject { - Reader(Document& d) : doc(d) {} - virtual ~Reader() {}; + GenericDocReader(GenericDocumentPtr d) : doc(d) {} + virtual ~GenericDocReader() {}; - Document& doc; - size_t token_pos = 0; - size_t line_num = 0; + GenericDocumentPtr doc; + uint32_t token_pos = 0; + uint32_t line_num = 0; bool MoveNext() { token_pos++; return EndOfFile(); } bool SeekNextLine(); - size_t CountLineArgs(); - bool EndOfFile(size_t offset = 0) const { return token_pos + offset >= doc.tokens.size(); } - - TokenType GetType(size_t offset = 0) const { return !EndOfFile(offset) ? doc.tokens[token_pos + offset].type : TokenType::NONE; } - const char* GetStringData(size_t offset = 0) const { return !EndOfFile(offset) ? (doc.string_pool.data() + (size_t)doc.tokens[token_pos + offset].data) : nullptr; } - float GetFloatData(size_t offset = 0) const { return !EndOfFile(offset) ? doc.tokens[token_pos + offset].data : 0.f; } - - const char* GetArgString(size_t offset = 0) const { ROR_ASSERT(IsArgString(offset)); return GetStringData(offset); } - float GetArgFloat(size_t offset = 0) const { ROR_ASSERT(IsArgFloat(offset)); return GetFloatData(offset); } - bool GetArgBool(size_t offset = 0) const { ROR_ASSERT(IsArgBool(offset)); return GetFloatData(offset) == 1.f; } - const char* GetArgKeyword(size_t offset = 0) const { ROR_ASSERT(IsArgKeyword(offset)); return GetStringData(offset); } - - bool IsArgString(size_t offset = 0) const { return GetType(offset) == TokenType::STRING; }; - bool IsArgFloat(size_t offset = 0) const { return GetType(offset) == TokenType::NUMBER; }; - bool IsArgBool(size_t offset = 0) const { return GetType(offset) == TokenType::BOOL; }; - bool IsArgKeyword(size_t offset = 0) const { return GetType(offset) == TokenType::KEYWORD; }; - + int CountLineArgs(); + bool EndOfFile(int offset = 0) const { return token_pos + offset >= doc->tokens.size(); } + + TokenType GetTokType(int offset = 0) const { return !EndOfFile(offset) ? doc->tokens[token_pos + offset].type : TokenType::NONE; } + const char* GetStringData(int offset = 0) const { return !EndOfFile(offset) ? (doc->string_pool.data() + (uint32_t)doc->tokens[token_pos + offset].data) : nullptr; } + float GetFloatData(int offset = 0) const { return !EndOfFile(offset) ? doc->tokens[token_pos + offset].data : 0.f; } + + const char* GetTokString(int offset = 0) const { ROR_ASSERT(IsTokString(offset)); return GetStringData(offset); } + float GetTokFloat(int offset = 0) const { ROR_ASSERT(IsTokFloat(offset)); return GetFloatData(offset); } + bool GetTokBool(int offset = 0) const { ROR_ASSERT(IsTokBool(offset)); return GetFloatData(offset) == 1.f; } + const char* GetTokKeyword(int offset = 0) const { ROR_ASSERT(IsTokKeyword(offset)); return GetStringData(offset); } + const char* GetTokComment(int offset = 0) const { ROR_ASSERT(IsTokComment(offset)); return GetStringData(offset); } + + bool IsTokString(int offset = 0) const { return GetTokType(offset) == TokenType::STRING; }; + bool IsTokFloat(int offset = 0) const { return GetTokType(offset) == TokenType::NUMBER; }; + bool IsTokBool(int offset = 0) const { return GetTokType(offset) == TokenType::BOOL; }; + bool IsTokKeyword(int offset = 0) const { return GetTokType(offset) == TokenType::KEYWORD; }; + bool IsTokComment(int offset = 0) const { return GetTokType(offset) == TokenType::COMMENT; }; }; +typedef RefCountingObjectPtr GenericDocReaderPtr; + } // namespace RoR \ No newline at end of file