LLayer Class

#include <Layers/llayer.h>
Inherits: LObject

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 .lyr files. See the language reference for more details. Examples in this document are rooted in the language syntax.

LLayer is the live runtime object whereas LLayerNode is 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.

Hierarchy and paths

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`

Acquiring root at depth

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()

Namespace layers

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`

Finding layers and attributes

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");

Enumerating contents

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

Inheriting another layer

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:

  • Base states that a derived attribute doesn't have are cloned.
  • Attributes only the base has are cloned into the derived layer.
  • Child layers only the base has are cloned in full.
  • Child layer order follows the base, with derived child layers going after.
  • A derived child that overrides an inherited child without its own << 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

Unresolved bases

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

Resolving attribute links

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).

LLayer * find_layer(const LString & path)

Finds a child layer by a path relative to this layer; returns nullptr if not found.

LLayer * find_layer(std::deque<LString> name_list)

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_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.

Appearance
Theme
—