17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 12:39:18 +02:00

perf(scenery): runs of model instances read straight from the text by a reader made for them, shared between threads; anything the reader doesn't cover left to the parser; model and skin located once for a run of instances; lookup of nodes by uuid filled on load only for a scenery opened for editing

This commit is contained in:
2026-10-06 21:58:49 +02:00
parent 96cd3e32ef
commit 90c5420396
9 changed files with 696 additions and 29 deletions

View File

@@ -198,6 +198,7 @@ set(SOURCES
"scene/scenelayers.cpp"
"scene/scenelayerssave.cpp"
"scene/sceneterrain.cpp"
"scene/scenemodelentries.cpp"
"simulation/simulationenvironment.cpp"
"simulation/simulationstateserializer.cpp"
"rendering/precipitation.cpp"

View File

@@ -291,12 +291,9 @@ TAnimModel::is_keyword( std::string const &Token ) const {
|| Token == "notransition";
}
bool TAnimModel::Load(cParser *parser, bool ter)
{ // rozpoznanie wpisu modelu i ustawienie świateł
std::string name = parser->getToken<std::string>();
std::string texture = parser->getToken<std::string>(false);
replace_slashes( name );
replace_slashes( texture );
// assigns the model and the replacable skin to the instance, and binds light and variant submodels of the model
void TAnimModel::assign_model( std::string &name, std::string const &texture, bool const ter )
{
if (!Init( name, texture ))
{
if (name != "notload")
@@ -329,6 +326,15 @@ bool TAnimModel::Load(cParser *parser, bool ter)
for (int i = 0; i < iMaxNumLights; ++i)
if (LightsOn[i] || LightsOff[i]) // Ra: zlikwidowałem wymóg istnienia obu
iNumLights = i + 1;
}
bool TAnimModel::Load(cParser *parser, bool ter)
{ // rozpoznanie wpisu modelu i ustawienie świateł
std::string name = parser->getToken<std::string>();
std::string texture = parser->getToken<std::string>(false);
replace_slashes( name );
replace_slashes( texture );
assign_model( name, texture, ter );
std::string token;
do {
@@ -400,6 +406,46 @@ bool TAnimModel::Load(cParser *parser, bool ter)
return true;
}
// sets up the instance from the values of a definition which was already taken apart, the way Load() does it from the text
void TAnimModel::Load( std::string const &Name, std::string const &Texture, TAnimModel const *Twin, glm::vec3 const *Angles, glm::vec3 const *Scale, bool const Transition )
{
if( Twin != nullptr
&& Twin->pModel != nullptr
&& ( Twin->m_materialdata.replacable_skins[ 1 ] != null_handle || Texture == "none" ) ) {
// the same model with the same skin was located for the twin already.
// NOTE: a model or a skin which couldn't be located is searched for and reported for each instance, like it always was
m_skintoken = Twin->m_skintoken;
m_materialdata = Twin->m_materialdata;
pModel = Twin->pModel;
LightsOn = Twin->LightsOn;
LightsOff = Twin->LightsOff;
sm_winter_variant = Twin->sm_winter_variant;
sm_spring_variant = Twin->sm_spring_variant;
sm_summer_variant = Twin->sm_summer_variant;
sm_autumn_variant = Twin->sm_autumn_variant;
iNumLights = Twin->iNumLights;
}
else {
auto name { Name };
assign_model( name, Texture, false );
}
// the optional blocks, each with the effect it has in Load()
if( Angles != nullptr ) {
vAngle = *Angles;
}
if( Scale != nullptr
&& Scale->x > 0.0f && Scale->y > 0.0f && Scale->z > 0.0f ) {
m_scale.x *= Scale->x;
m_scale.y *= Scale->y;
m_scale.z *= Scale->z;
}
if( false == Transition ) {
m_transition = false;
}
update_instanceable_flag();
}
namespace {
// returns true if this animation type mutates per-instance state on the shared
// TSubModel tree, which would make batched rendering unsafe. Most animations

View File

@@ -118,6 +118,11 @@ public:
static void AnimUpdate( double dt );
bool Init(std::string const &asName, std::string const &asReplacableTexture);
bool Load(cParser *parser, bool ter = false);
// sets up the instance from the values of a definition which was already taken apart, the way Load(cParser *) does it
// from the text. Name, Texture: as that method gets them ready, lower case model name and forward slashes in both.
// Twin: instance set up for the same model and texture, to take them after instead of locating them again; can be nullptr.
// Angles, Scale: content of the optional blocks of the definition, nullptr for a block which isn't there
void Load( std::string const &Name, std::string const &Texture, TAnimModel const *Twin, glm::vec3 const *Angles, glm::vec3 const *Scale, bool const Transition );
std::shared_ptr<TAnimContainer> AddContainer(std::string const &Name);
std::shared_ptr<TAnimContainer> GetContainer(std::string const &Name = "");
void LightSet( int const n, float const v );
@@ -226,6 +231,8 @@ public:
bool m_instanceable { false };
// helper: evaluates current state and updates m_instanceable accordingly.
void update_instanceable_flag();
// helper: assigns the model and the replacable skin to the instance, and binds light and variant submodels of the model
void assign_model( std::string &name, std::string const &texture, bool const ter );
// diagnostic counters (process-wide). Updated inside update_instanceable_flag()
// so the renderer can surface load-time classification stats in the debug overlay.

401
scene/scenemodelentries.cpp Normal file
View File

@@ -0,0 +1,401 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "scene/scenemodelentries.h"
#include "utilities/parser.h"
#include <algorithm>
#include <array>
#include <future>
#include <system_error>
#include <thread>
#include <unordered_map>
namespace scene
{
namespace
{
enum char_kind : unsigned char
{
char_regular,
char_separator, // one of the separators the scenery parser splits the text on
char_special // can make the parser treat the token in a way of its own: a quote, or a part of a comment mark
};
constexpr std::array<unsigned char, 256> make_char_kinds()
{
std::array<unsigned char, 256> kinds{};
kinds['\n'] = char_separator;
kinds['\r'] = char_separator;
kinds['\t'] = char_separator;
kinds[' '] = char_separator;
kinds[';'] = char_separator;
kinds['"'] = char_special;
kinds['/'] = char_special;
return kinds;
}
constexpr std::array<unsigned char, 256> char_kinds{make_char_kinds()};
char to_lower(char const Character)
{
// only the ascii letters, like the parser does it
return (Character >= 'A' && Character <= 'Z') ? static_cast<char>(Character - 'A' + 'a') : Character;
}
// compares a token with a lower case keyword the way a token converted by the parser would compare
bool equals(std::string_view const Token, std::string_view const Keyword)
{
if (Token.size() != Keyword.size())
{
return false;
}
for (std::size_t idx = 0; idx < Token.size(); ++idx)
{
if (to_lower(Token[idx]) != Keyword[idx])
{
return false;
}
}
return true;
}
// reads the definitions held by one piece of a text
class entry_reader
{
public:
explicit entry_reader(std::string_view const Text) : m_text(Text) {}
// reads definitions from specified place in the text, until the end of one reaches Limit or something else turns up.
// NOTE: line breaks are counted from the place the reading starts at
void read(std::size_t Begin, std::size_t Limit, bool Keywordread);
// members
model_entry_block block;
std::size_t cursor{0}; // end of the last definition read, or the place the reading started at
std::size_t linebreaks{0}; // line breaks ahead of the cursor
bool complete{false}; // the reading got as far as the limit
private:
// moves past the separators and the line comments. returns: false at a block comment
bool skip(std::size_t &Position, std::size_t &Linebreaks) const;
// takes the token at specified place, which has to be past the separators. returns: false if the parser wouldn't take
// the token the way it's written: there's a quote in it, or a comment starts in its middle
bool token(std::size_t &Position, std::string_view &Token) const;
// takes the next token. returns: false if there isn't one which can be used as it's written
bool next(std::size_t &Position, std::size_t &Linebreaks, std::string_view &Token) const
{
return skip(Position, Linebreaks) && token(Position, Token) && false == Token.empty();
}
// takes the next token as a number. returns: false unless the parser would convert the token without a stream
template <typename Type_> bool number(std::size_t &Position, std::size_t &Linebreaks, Type_ &Value) const
{
std::string_view text;
return next(Position, Linebreaks, text) && cParser::parseNumber(text, Value);
}
// takes a complete definition. returns: false if what's there isn't one, or isn't one the reader covers
bool entry(std::size_t &Position, std::size_t &Linebreaks, bool Keywordread, model_entry &Entry);
std::uint32_t appearance(std::string_view Model, std::string_view Texture);
// members
std::string_view m_text;
std::unordered_map<std::string_view, std::uint32_t> m_appearances; // by the part of the text which names the model and the texture
std::string_view m_recentkey;
std::uint32_t m_recentappearance{0};
};
bool entry_reader::skip(std::size_t &Position, std::size_t &Linebreaks) const
{
auto const *const data{m_text.data()};
auto const size{m_text.size()};
while (Position < size)
{
auto const character{data[Position]};
if (char_kinds[static_cast<unsigned char>(character)] == char_separator)
{
if (character == '\n')
{
++Linebreaks;
}
++Position;
continue;
}
if (character == '/' && Position + 1 < size)
{
if (data[Position + 1] == '/')
{
// the comment takes the rest of the line, along with the line break
auto const end{m_text.find('\n', Position + 2)};
if (end == std::string_view::npos)
{
Position = size;
}
else
{
Position = end + 1;
++Linebreaks;
}
continue;
}
if (data[Position + 1] == '*')
{
return false;
}
}
break;
}
return true;
}
bool entry_reader::token(std::size_t &Position, std::string_view &Token) const
{
auto const *const data{m_text.data()};
auto const size{m_text.size()};
auto const begin{Position};
while (Position < size)
{
auto const kind{char_kinds[static_cast<unsigned char>(data[Position])]};
if (kind == char_separator)
{
break;
}
if (kind == char_special)
{
if (data[Position] == '"')
{
return false;
}
if (Position + 1 < size && (data[Position + 1] == '/' || data[Position + 1] == '*'))
{
return false;
}
}
++Position;
}
Token = m_text.substr(begin, Position - begin);
return true;
}
bool entry_reader::entry(std::size_t &Position, std::size_t &Linebreaks, bool const Keywordread, model_entry &Entry)
{
std::string_view word;
if (false == Keywordread)
{
if (false == next(Position, Linebreaks, word) || false == equals(word, "node"))
{
return false;
}
Entry.begin = Position - word.size();
// the parser takes a token along with the separator which ends it, and the loader notes the line right after
Entry.line = Linebreaks + ((Position < m_text.size() && m_text[Position] == '\n') ? 1 : 0);
}
// NOTE: negative minimal range marks the model as terrain, which is processed in a way of its own
if (false == number(Position, Linebreaks, Entry.range_max) || false == number(Position, Linebreaks, Entry.range_min) || Entry.range_min < 0.0)
{
return false;
}
// NOTE: the parser takes include for a directive wherever it shows up
if (false == next(Position, Linebreaks, Entry.name) || equals(Entry.name, "include"))
{
return false;
}
if (false == next(Position, Linebreaks, word) || false == equals(word, "model"))
{
return false;
}
if (false == number(Position, Linebreaks, Entry.location.x) || false == number(Position, Linebreaks, Entry.location.y) || false == number(Position, Linebreaks, Entry.location.z) ||
false == number(Position, Linebreaks, Entry.angle))
{
return false;
}
std::string_view model;
std::string_view texture;
if (false == next(Position, Linebreaks, model) || equals(model, "include") || false == next(Position, Linebreaks, texture) || equals(texture, "include"))
{
return false;
}
while (true)
{
if (false == next(Position, Linebreaks, word))
{
return false;
}
if (equals(word, "endmodel"))
{
break;
}
if (equals(word, "angles"))
{
// NOTE: a block which shows up again is left to the parser, along with the question of what it means
if (Entry.has_angles || false == number(Position, Linebreaks, Entry.angles.x) || false == number(Position, Linebreaks, Entry.angles.y) || false == number(Position, Linebreaks, Entry.angles.z))
{
return false;
}
Entry.has_angles = true;
}
else if (equals(word, "scale"))
{
if (Entry.has_scale || false == number(Position, Linebreaks, Entry.scale.x) || false == number(Position, Linebreaks, Entry.scale.y) || false == number(Position, Linebreaks, Entry.scale.z))
{
return false;
}
Entry.has_scale = true;
}
else if (equals(word, "notransition"))
{
Entry.notransition = true;
}
else
{
return false;
}
}
Entry.end = Position;
Entry.appearance = appearance(model, texture);
return true;
}
std::uint32_t entry_reader::appearance(std::string_view const Model, std::string_view const Texture)
{
// the two tokens along with whatever is between them. the same pair written in another way gets an entry of its own
std::string_view const key{Model.data(), static_cast<std::size_t>((Texture.data() + Texture.size()) - Model.data())};
if (key == m_recentkey)
{
return m_recentappearance;
}
auto lookup{m_appearances.find(key)};
if (lookup == m_appearances.end())
{
model_appearance appearance;
appearance.model.assign(Model);
std::transform(std::begin(appearance.model), std::end(appearance.model), std::begin(appearance.model), [](char const Character) { return Character == '\\' ? '/' : to_lower(Character); });
appearance.texture.assign(Texture);
std::replace(std::begin(appearance.texture), std::end(appearance.texture), '\\', '/');
block.appearances.emplace_back(std::move(appearance));
lookup = m_appearances.emplace(key, static_cast<std::uint32_t>(block.appearances.size() - 1)).first;
}
m_recentkey = key;
m_recentappearance = lookup->second;
return lookup->second;
}
void entry_reader::read(std::size_t const Begin, std::size_t const Limit, bool Keywordread)
{
cursor = Begin;
linebreaks = 0;
while (cursor < Limit)
{
auto position{cursor};
auto breaks{linebreaks};
model_entry definition;
if (false == entry(position, breaks, Keywordread, definition))
{
break;
}
Keywordread = false;
block.entries.emplace_back(definition);
cursor = position;
linebreaks = breaks;
}
complete = (cursor >= Limit);
}
} // namespace
model_entry_run read_model_entries(std::string_view const Text, bool const Keywordread, model_reader_setup const &Setup)
{
model_entry_run run;
entry_reader lead{Text};
lead.read(0, std::min(Text.size(), Setup.lead), Keywordread);
if (lead.block.entries.empty())
{
return run;
}
auto cursor{lead.cursor};
auto linebreaks{lead.linebreaks};
run.blocks.emplace_back(std::move(lead.block));
if (lead.complete && cursor < Text.size())
{
// the run goes on: the next part of the text is divided between the threads. a thread needs a place to start at,
// which is found by looking for the keyword ending a definition. whether the place really is an end of a definition
// is known only when the thread reading the part ahead of it gets there, so the pieces are put together as long as
// each one ended right where the next one began. if that's not the case, e.g. the place found was inside a comment,
// the pieces past it are dropped and their text is read again by the next call, starting from a place which is certain
auto const limit{cursor + std::min(Text.size() - cursor, std::max<std::size_t>(Setup.window, 1))};
auto const threads{std::clamp<std::size_t>(Setup.threads != 0 ? Setup.threads : std::thread::hardware_concurrency(), 1, 16)};
auto const count{std::clamp<std::size_t>((limit - cursor) / std::max<std::size_t>(Setup.share, 1), 1, threads)};
std::string_view const endmark{"endmodel"};
std::vector<std::size_t> bounds{cursor};
for (std::size_t idx = 1; idx < count; ++idx)
{
auto const mark{Text.find(endmark, cursor + (limit - cursor) / count * idx)};
if (mark == std::string_view::npos || mark + endmark.size() >= limit)
{
break;
}
if (mark + endmark.size() > bounds.back())
{
bounds.emplace_back(mark + endmark.size());
}
}
bounds.emplace_back(limit);
std::vector<entry_reader> readers(bounds.size() - 1, entry_reader{Text});
{
std::vector<std::future<void>> tasks;
for (std::size_t idx = 1; idx < readers.size(); ++idx)
{
auto const work{[&readers, &bounds, idx]() { readers[idx].read(bounds[idx], bounds[idx + 1], false); }};
try
{
tasks.emplace_back(std::async(std::launch::async, work));
}
catch (std::system_error const &)
{
// no thread to be had, the piece is read right here
work();
}
}
readers.front().read(bounds[0], bounds[1], false);
for (auto &task : tasks)
{
task.get();
}
}
for (std::size_t idx = 0; idx < readers.size(); ++idx)
{
auto &reader{readers[idx]};
for (auto &definition : reader.block.entries)
{
definition.line += linebreaks;
}
cursor = reader.cursor;
linebreaks += reader.linebreaks;
if (false == reader.block.entries.empty())
{
run.blocks.emplace_back(std::move(reader.block));
}
if (reader.cursor != bounds[idx + 1])
{
break;
}
}
}
run.length = cursor;
run.linebreaks = linebreaks;
return run;
}
} // namespace scene
//---------------------------------------------------------------------------

81
scene/scenemodelentries.h Normal file
View File

@@ -0,0 +1,81 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace scene
{
// model file and replacable skin of a model instance, in the form TAnimModel::Load() gives the tokens which name them
struct model_appearance
{
std::string model; // lower case, forward slashes
std::string texture; // forward slashes
};
// definition of a model instance read from scenery text:
// node <max range> <min range> <name> model <x> <y> <z> <angle> <model> <texture> [angles <x> <y> <z>] [scale <x> <y> <z>] [notransition] endmodel
struct model_entry
{
double range_max{0.0};
double range_min{0.0};
std::string_view name; // the way the text spells it. NOTE: refers to the text
glm::dvec3 location{0.0};
float angle{0.f}; // rotation around the vertical axis
glm::vec3 angles{0.f}; // content of the angles block, if there's one
glm::vec3 scale{1.f}; // content of the scale block, if there's one
bool has_angles{false};
bool has_scale{false};
bool notransition{false};
std::uint32_t appearance{0}; // model and texture of the instance, index into the appearances of the block holding the entry
std::size_t begin{0}; // location of the definition in the text: from the start of its node keyword...
std::size_t end{0}; // ...to the end of its closing keyword
std::size_t line{0}; // line breaks in the text ahead of the definition, counted the way the parser counts them for a node
};
// definitions read from one piece of the text
struct model_entry_block
{
std::vector<model_entry> entries;
std::vector<model_appearance> appearances;
};
// run of definitions a text starts with
struct model_entry_run
{
std::vector<model_entry_block> blocks; // in the order of the text; no block is empty
std::size_t length{0}; // amount of the text taken by the definitions
std::size_t linebreaks{0}; // number of line breaks in that part of the text
};
// how the text is divided between the threads
struct model_reader_setup
{
std::size_t lead{64 * 1024}; // amount of the text read before the threads get involved; most runs of definitions are short
std::size_t window{8 * 1024 * 1024}; // most of the text a single call goes through past the lead
std::size_t share{256 * 1024}; // least amount of the text worth a thread of its own
unsigned int threads{0}; // most threads put to work; 0: as many as the hardware runs at a time
};
// reads the definitions of model instances provided text starts with. the reading ends where the text holds something else
// than a definition the reader knows inside out: another kind of node or directive, a block comment, a definition of
// terrain, one with lights or anything else not listed above, with quoted text, an include or a comment glued to a token.
// that part is left for the scenery parser, so the outcome is the same as if the parser went through all of the text.
// Keywordread: the node keyword of the first definition was already taken off the text; begin and line of that entry aren't set
model_entry_run read_model_entries(std::string_view Text, bool Keywordread, model_reader_setup const &Setup = model_reader_setup());
} // namespace scene
//---------------------------------------------------------------------------

View File

@@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/.
#include "scene/scenenodegroups.h"
#include "scene/scenelayers.h"
#include "scene/sceneterrain.h"
#include "scene/scenemodelentries.h"
#include "rendering/particles.h"
#include "world/Event.h"
#include "world/MemCell.h"
@@ -484,6 +485,8 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
// defined by a template, or by something else which isn't a scenery layer file
nodedata.instance = ( scene::Layers.instance() != 0 ? scene::Layers.instance() : scene::untracked_instance );
}
// a run of plain model instances is taken straight from the text, instead of token by token
if( true == deserialize_models( Input, Scratchpad, nodedata, inputline, sourcebegin ) ) { return; }
// common data and node type indicator
Input.getTokens( 4 );
Input
@@ -667,25 +670,7 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
// model import can potentially fail
if( instance == nullptr ) { return; }
if( instance->Model() != nullptr ) {
for( auto const &smokesource : instance->Model()->smoke_sources() ) {
Particles.insert(
smokesource.first,
instance,
smokesource.second );
}
}
if( false == simulation::Instances.insert( instance ) ) {
ErrorLog( "Bad scenario: duplicate 3d model instance name \"" + instance->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
}
scene::Groups.insert( scene::Groups.handle(), instance );
simulation::Region->insert( instance );
scene::Layers.track( instance, { sourcebegin, Input.TokenEnd() }, instance->Angles(), instance->Scale() );
scene::basic_node *hierarchy_node = instance;
if (hierarchy_node)
{ scene::Hierarchy[hierarchy_node->uuid.to_string()] = hierarchy_node;
}
insert_model( instance, Input, inputline, { sourcebegin, Input.TokenEnd() } );
}
}
else if( nodedata.type == "triangles"
@@ -825,6 +810,99 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
}
}
// makes model instance defined by a scenery file a part of the simulation
void
state_serializer::insert_model( TAnimModel *Instance, cParser const &Input, std::size_t const Line, scene::source_span const &Span ) {
if( Instance->Model() != nullptr ) {
for( auto const &smokesource : Instance->Model()->smoke_sources() ) {
Particles.insert(
smokesource.first,
Instance,
smokesource.second );
}
}
if( false == simulation::Instances.insert( Instance ) ) {
ErrorLog( "Bad scenario: duplicate 3d model instance name \"" + Instance->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( Line ) + ")" );
}
scene::Groups.insert( scene::Groups.handle(), Instance );
simulation::Region->insert( Instance );
scene::Layers.track( Instance, Span, Instance->Angles(), Instance->Scale() );
// the lookup by uuid serves the scenery editor. it's filled only for a scenery opened for editing, as with a lot
// of instances it takes more time than everything else done for an instance put together
if( true == Global.editor_session ) {
scene::Hierarchy[ Instance->uuid.to_string() ] = Instance;
}
}
// loads a run of model instances straight from the text of the scenery file, starting with the instance whose node keyword
// was just read. the text is taken apart by a reader made for these definitions, with a few threads sharing the work; the
// instances are then created here one by one, in the order of the text. returns: true if any instances were loaded, false
// if the definition at hand has to go through the parser
bool
state_serializer::deserialize_models( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data &Nodedata, std::size_t const Line, std::streamoff const Sourcebegin ) {
auto const text { Input.remainingText() };
if( true == text.empty() ) { return false; }
auto const run { scene::read_model_entries( text, true ) };
if( true == run.blocks.empty() ) { return false; }
// NOTE: the parser moves on but the text stays where it is, the definitions refer to it
Input.skipText( run.length, run.linebreaks, std::string_view { "endmodel" }.size() );
auto const textbegin { Input.TokenEnd() - static_cast<std::streamoff>( run.length ) };
Nodedata.type = "model";
auto loaded { 0 };
for( auto const &block : run.blocks ) {
// instances of the same model with the same skin are set up after the first one of them
std::vector<TAnimModel const *> twins( block.appearances.size(), nullptr );
for( auto const &entry : block.entries ) {
Nodedata.range_max = entry.range_max;
Nodedata.range_min = entry.range_min;
// the parser supplies the names in lower case, which goes only for the ascii letters
Nodedata.name.assign( entry.name );
for( auto &character : Nodedata.name ) {
if( character >= 'A' && character <= 'Z' ) {
character = static_cast<char>( character - 'A' + 'a' );
}
}
if( Nodedata.name == "none" ) { Nodedata.name.clear(); }
// what follows is what deserialize_model() does
auto *instance = new TAnimModel( Nodedata );
instance->Angles( Scratchpad.location.rotation + glm::vec3 { 0.f, entry.angle, 0.f } );
if( false == Scratchpad.location.scale.empty() ) {
instance->Scale( Scratchpad.location.scale.top() );
}
auto const &appearance { block.appearances[ entry.appearance ] };
auto &twin { twins[ entry.appearance ] };
instance->Load(
appearance.model, appearance.texture, twin,
( entry.has_angles ? &entry.angles : nullptr ),
( entry.has_scale ? &entry.scale : nullptr ),
false == entry.notransition );
if( twin == nullptr ) { twin = instance; }
instance->location( transform( entry.location, Scratchpad ) );
// location of the first definition is known from its node keyword, which went through the parser
auto const first { loaded == 0 };
insert_model(
instance, Input,
( first ? Line : Line + entry.line ),
{ ( first ? Sourcebegin : textbegin + static_cast<std::streamoff>( entry.begin ) ), textbegin + static_cast<std::streamoff>( entry.end ) } );
++loaded;
}
}
if( Nodedata.layer != null_handle ) {
// scenery opened for editing: keep count of what the scenery file contains
scene::Layers.count( Nodedata.layer, scene::layer_item::model, loaded );
}
return true;
}
namespace {
// passes the placement in effect to the layer bookkeeping of scenery opened for editing

View File

@@ -86,6 +86,10 @@ private:
TMemCell * deserialize_memorycell( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
TEventLauncher * deserialize_eventlauncher( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
TAnimModel * deserialize_model( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
// loads a run of model instances straight from the text. returns: true if any were loaded, false if the definition at hand is left to the parser
bool deserialize_models( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data &Nodedata, std::size_t Line, std::streamoff Sourcebegin );
// makes model instance defined by a scenery file a part of the simulation
void insert_model( TAnimModel *Instance, cParser const &Input, std::size_t Line, scene::source_span const &Span );
TDynamicObject * deserialize_dynamic( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
sound_source * deserialize_sound( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata );
void init_time();

View File

@@ -832,3 +832,44 @@ bool cParser::InLayerFile() const
{
return mIncludeParser ? mIncludeParser->InLayerFile() : (false == mFile.empty() && false == mIncFile);
}
std::string_view cParser::remainingText() const
{
if (false == tokens.empty())
{
return {};
}
// each parser on the way gets to change the tokens of the file being read
auto const *source{this};
while (true)
{
if (false == source->parameters.empty() || false == source->skipComments || source->mComments.size() != 2)
{
return {};
}
if (source->mIncludeParser == nullptr)
{
break;
}
source = source->mIncludeParser.get();
}
// NOTE: the first token of a file is left to the regular code, which deals with the byte order mark
if (source->mFirstToken || source->mEofBit || source->mFailBit || static_cast<std::size_t>(source->mPosition) >= source->mBuffer.size())
{
return {};
}
return std::string_view{source->mBuffer}.substr(static_cast<std::size_t>(source->mPosition));
}
void cParser::skipText(std::size_t const Count, std::size_t const Lines, std::size_t const Tokenlength)
{
if (mIncludeParser)
{
mIncludeParser->skipText(Count, Lines, Tokenlength);
return;
}
mPosition = static_cast<std::streamoff>(std::min(static_cast<std::size_t>(mPosition) + Count, mBuffer.size()));
mLine += Lines;
mTokenEnd = mPosition;
mTokenBegin = mPosition - static_cast<std::streamoff>(std::min(Tokenlength, static_cast<std::size_t>(mPosition)));
}

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <string>
#include <string_view>
#include <sstream>
#include <fstream>
#include <vector>
@@ -107,6 +108,16 @@ class cParser //: public std::stringstream
std::streamoff TokenEnd() const;
// true if the most recently read token came straight from a scenery file which isn't an *.inc node template
bool InLayerFile() const;
// text which follows the most recently read token, for the readers which take entries of a known kind straight from
// the text instead of token by token. empty if the tokens can't be taken that way: some wait to be picked up, there
// are parameters of an include to put in them, or comment marks other than the standard ones are in use
std::string_view remainingText() const;
// moves past specified amount of the text returned by remainingText(): Lines is the number of line breaks in it,
// Tokenlength the length of the token it ends with
void skipText(std::size_t Count, std::size_t Lines, std::size_t Tokenlength);
// converts text holding a plain number without involving a stream. returns: true on success, false if the text needs regular treatment
template <typename Type_>
static bool parseNumber( std::string_view Token, Type_ &Output );
bool expandIncludes = true;
bool allowRandomIncludes = false;
bool skipComments = true;
@@ -158,9 +169,6 @@ class cParser //: public std::stringstream
bool hasChar();
bool getChar( char &Char );
void updateCharClasses( char const *Break );
// converts text holding a plain number without involving a stream. returns: true on success, false if the text needs regular treatment
template <typename Type_>
static bool parseNumber( std::string const &Token, Type_ &Output );
// members:
bool m_autoclear { true }; // unretrieved tokens are discarded when another read command is issued (legacy behaviour)
bool LoadTraction { true }; // load traction?
@@ -202,7 +210,7 @@ cParser::getToken( bool const ToLower, const char *Break );
template<typename Type_>
bool
cParser::parseNumber( std::string const &Token, Type_ &Output ) {
cParser::parseNumber( std::string_view const Token, Type_ &Output ) {
using type = std::remove_cv_t<Type_>;
if constexpr(