Skip to content

Commit

Permalink
Added GenericDocument + Reader API to AngelScript
Browse files Browse the repository at this point in the history
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()
  • Loading branch information
ohlidalp committed Dec 9, 2022
1 parent b6a3516 commit ea12f04
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 49 deletions.
1 change: 1 addition & 0 deletions source/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions source/main/physics/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4650,3 +4650,8 @@ void Actor::UpdatePropAnimInputEvents()
state.event_active_prev = ev_active;
}
}

std::string Actor::getTruckFileResourceGroup()
{
return m_gfx_actor->GetResourceGroup();
}
1 change: 1 addition & 0 deletions source/main/physics/Actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
1 change: 1 addition & 0 deletions source/main/scripting/ScriptEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions source/main/scripting/bindings/ActorAngelscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions source/main/scripting/bindings/AngelScriptBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
113 changes: 113 additions & 0 deletions source/main/scripting/bindings/GenericFileFormatAngelscript.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

/// @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);
}
2 changes: 1 addition & 1 deletion source/main/scripting/bindings/ImGuiAngelscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
73 changes: 52 additions & 21 deletions source/main/utils/GenericFileFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -668,28 +668,59 @@ 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();
}

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;
}

Loading

0 comments on commit ea12f04

Please sign in to comment.