Public Functions
| LLayer() | |
| LLayer(const LString & name) | |
| ~LLayer() | |
| LAttributes | attributes() |
| LLayer * | base() |
| LString | base_path() |
| LLayers | children() |
| void | clone_to(LObject * parent) |
| std::set<LLayer *> | dependencies() |
| void | finalize() |
| LAttribute * | find_attribute(std::string_view attr_name) |
| LAttribute * | find_attribute(const LString & attr_name) |
| LAttribute * | find_attribute(const char * attr_name) |
| LLayer * | find_layer(const LString & path) |
| LLayer * | find_layer(std::deque<LString> name_list) |
| bool | has_unresolved_base() |
| LLayer * | namespace_root() |
| LString | path() |
| LLayer * | parent() |
| void | resolve_links() |
| void | resolve_bases() |
| LLayer * | root() |
| void | set_base_path(const LString & base_path) |
Detailed Description
A layer is a composite runtime object described by its attributes and child layers, created by the loader and maintained by the engine.
Layers are declared in
.lyrfiles. See the language reference for more details. Examples in this document are rooted in the language syntax.
LLayeris the live runtime object whereasLLayerNodeis the parsed declaration it is built from (see LParser). Since a single declaration can name several targets through expansion, one node can produce several live layers.
Layers are hierarchical components that can be identified by their paths.
A path joins transitive parent layer names with the identifying layer's own
name at the end, separated with forward-slashes (/). Top-level layers are
found within the engine's root (no leading / in the path). Consider the
following:
Grandfather: // Layer
Father: // Layer; child of `Grandfather`
Son: // Layer; child of `Father`
Retired: false // Attribute; child of `Son`
// `Son` is a child of `Father`, but doesn't inherit from him;
// inheritance is handled separately with `<<`.
Once loaded, layer paths can be acquired with path().
LLayer* grandfather = engine.find_layer("Grandfather");
LLayer* father = engine.find_layer("Grandfather/Father");
LLayer* son = engine.find_layer("Grandfather/Father/Son");
LString grandfather_path = grandfather->path(); // "Grandfather"
LString father_path = father->path(); // "Grandfather/Father"
LString son_path = son->path(); // "Grandfather/Father/Son"
Parent layers can be acquired with parent(). All layers loaded into the
engine have a parent except for the engine's root where calling parent()
returns nullptr.
LLayer* son_parent = son->parent(); // Returns `father`
LLayer* father_parent = father->parent(); // Returns `grandfather`
LLayer* grandfather_parent = grandfather->parent(); // Returns engine.root()
LLayer* root_parent = engine.root()->parent(); // Returns `nullptr`
A layer's associated root layer (typically the engine root, although it
doesn't necessarily have to be) can be acquired with root().
LLayer* associated_root = grandfather->root(); // Returns engine.root()
associated_root = father->root(); // Also returns engine.root()
associated_root = son->root(); // Also returns engine.root()
A namespace is an optional, special top-level layer that the engine creates and loads file-level layers into. Namespaces reduce the risk of name clashes between different sets of files.
The above C++ examples all assume that the .lyr example was loaded without
a namespace provided. Let's assume that it was instead loaded under the
Family namespace (namespace names are passed to LEngine::load_files()).
This changes Grandfather's parent to the Family namespace:
LLayer* family = grandfather->parent(); // Returns `Family` namespace
The namespace layer can be acquired from any child at any depth by calling
namespace_root().
family = grandfather->namespace_root(); // Also returns `Family`
family = father->namespace_root(); // Also returns `Family`
family = son->namespace_root(); // Also returns `Family`
Consider the following layer declarations:
Save:
Hero:
Level: 4
(,Max) Health: 120 // Health, Max Health
Inventory:
Gold: 270
Potion/Count: 5
Child layers can be acquired by passing their paths to find_layer().
The path must be relative to the calling layer.
LLayer* hero = engine.find_layer("Save/Hero"); // From root
LLayer* potion = hero->find_layer("Inventory/Potion"); // From `Hero`
potion = engine.find_layer("Save/Hero/Inventory/Potion"); // From root
Attributes can be acquired by passing their names to find_attribute().
LAttribute* hero_level = hero->find_attribute("Level");
LAttribute* potion_count = potion->find_attribute("Count");
As an LObject, children can be acquired with LObject::find_children<T>(),
where T is typically LLayer or LAttribute. Children maintain their
declaration order (see LEngine), and if recursive is enabled, each child
is followed immediately by its descendants.
LLayers hero_layers = hero->find_children<LLayer>();
// {`Inventory`}
LLayers hero_layers_deep = hero->find_children<LLayer>(true); // Recursive
// {`Inventory`, `Potion`}
LAttributes hero_attributes = hero->find_children<LAttribute>();
// {`Level`,`Health`,`Max Health`}
Children can also be acquired through children() and attributes(), which
return the same as the non-recursive find_children<T>() calls.
hero_layers = hero->children();
// {`Inventory`}
hero_attributes = hero->attributes();
// {`Level`,`Health`,`Max Health`}
A layer declaration can reference a base layer to inherit from, producing a copy of the base and merging the derived's content in. Consider the following:
Player:
Level: 1
Class: "Knight"
(,Max) (Health,Stamina): 100 // Health, Max Health, Stamina, Max Stamina
Inventory/Gold: 0
Save:
Hero << Player:
Level: 4
(,Max) Health: 120
Inventory:
Gold: 270
Potion/Count: 5
In the above, Hero inherits Player. Derived child layers and attributes
defined with the same name as their base counterparts are merged together,
with derived attribute values winning. The following are the merge rules:
<<
takes the inherited child as its base.Inheritance works by setting base paths with set_base_path() (handled by
the loader), resolving them with resolve_bases() (recursive), and merging
everything with finalize() (also recursive). The engine resolves and
finalizes bases for each set of files (each call to LEngine::load_files()).
When building a layer tree by hand (without using the engine), the
resolve_bases() and finalize() methods must be called manually.
Importantly, inheritance depends on load order. If a layer in one set of files depends on a base from another set, the set containing the base must be loaded first.
LLayer* hero = engine.find_layer("Save/Hero");
LString hero_base_path = hero->base_path(); // "Player"
LLayer* hero_base = hero->base(); // `Player` layer
LAttribute* hero_class = hero->find_attribute("Class"); // Inherited
LString class_name = hero_class->as<LString>(); // "Knight"
LAttribute* hero_stamina = hero->find_attribute("Stamina"); // Inherited
double stamina_value = hero_stamina->as<double>(); // 100.0
LAttribute* hero_health = hero->find_attribute("Health"); // Overridden
double health_value = hero_health->as<double>(); // 120.0
LAttribute* hero_level = hero->find_attribute("Level"); // Overridden
double level_value = hero_level->as<double>(); // 4.0
When bases fail to resolve, base() stays nullptr and nothing gets merged.
A base that would form a cycle (a layer inheriting itself, an ancestor, a
layer that already inherits it, or a layer with a child that inherits it
back) is also rejected, but only for one declaration in the cycle, and
which one depends on declaration order. That layer's base() stays
nullptr and nothing gets merged into it, while the rest of the cycle
keeps its bases. If a layer has an unresolved base (a base path set and
nullptr as base()), then calling has_unresolved_base() returns true.
// Given `some_layer` is a layer without a base ...
some_layer->set_base_path("Not/A/Real/Base");
some_layer->resolve_bases();
bool unresolved = some_layer->has_unresolved_base(); // `unresolved` is true
resolve_links() is available to resolve attribute links recursively
through all child layers. However, instead of manually calling it per layer,
use LEngine::resolve_links() which traverses the whole tree.
Member Function Documentation
LLayer()
Constructs an empty layer.
LLayer(const LString & name)
Constructs a layer with a name.
~LLayer()
No description available.
LAttributes attributes()
Returns a vector of this layer's child attributes in declaration order.
LLayer * base()
Returns the base layer that this layer inherits from.
Returns nullptr if this layer has no base.
LString base_path()
Returns the path to this layer's base, if one has been set with
set_base_path(); otherwise returns an empty string.
LLayers children()
Returns a vector of this layer's child layers in declaration order.
void clone_to(LObject * parent)
Produces a clone of this layer as a child of the given parent.
This is used by finalize() for deep copying the inherited base layer.
std::set<LLayer *> dependencies()
Returns the base layer plus any child layer dependencies (recursive); does not return the full base chain (dependencies of bases).
void finalize()
Finalizes the layer after loading and base resolution.
Finalizes the base, then child layers, then merges the base into this layer.
If the base is still being finalized when this layer is reached, the two depend on each other through a cycle, so the base is rejected and nothing is merged.
LAttribute * find_attribute(std::string_view attr_name)
Finds a child attribute by name; returns nullptr if not found.
Lookups are served from a per-layer name map that is rebuilt only when
the layer's child set changes (see LObject::children_version), so
repeated lookups (like a rendering hot path) cost one map find instead
of a scan over freshly-enumerated children.
LAttribute * find_attribute(const LString & attr_name)
Finds a child attribute by name; returns nullptr if not found.
Overload for find_attribute(std::string_view).
LAttribute * find_attribute(const char * attr_name)
Finds a child attribute by name; returns nullptr if not found.
Overload for find_attribute(std::string_view).
Finds a child layer by a path relative to this layer; returns nullptr
if not found.
Finds a child layer by a list of names composing a path relative to
this layer; returns nullptr if not found.
bool has_unresolved_base()
Returns true if this layer's base hasn't been resolved yet, failed to resolve, or was rejected as a cycle (during resolution or finalization).
LLayer * namespace_root()
Returns the namespace associated with this layer. If no namespace name
is provided to LEngine::load_files(), then this method returns this
layer's top-level ancestor (or itself if it has no ancestor).
Base and link resolution scope to the namespace before the root so that layers under one namespace don't collide with another's.
LString path()
Returns the full path of this layer.
LLayer * parent()
Returns the parent layer in the hierarchy.
Returns nullptr if this is a root layer.
void resolve_links()
Resolves all attribute links within this layer.
void resolve_bases()
Resolves this layer's base, and its children, recursively. Layers whose
bases have already resolved are skipped (its children still resolve).
A base whose chain leads back to this layer or one of its parents is
rejected. Cycles this check can't see yet (those closed by a base that
resolves later, or running through child layers) are rejected by
finalize() instead.
Resolution lookup is scoped to the namespace first, then falls back to the root.
LLayer * root()
Returns the associated root of this layer.
Returns this layer itself when it is the root (has no parent layer).
void set_base_path(const LString & base_path)
Sets the path of the base layer to inherit from, without resolving it.
If this layer's base has already been resolved, base() doesn't change
since resolve_bases() skips it.