Registries and self-registration#

ng::Registry<Key, Value> (registry.h) is a std::map wrapped in a singleton: there is exactly one instance per (Key, Value) pair, reachable with instance().

Alias (types.h)

Key -> Value

Used by

NodesRegistry

qualifier_t -> NodeDefinition

Graph::add_node

ValidatorsRegistry

socket_t (a single bit) -> validator_t

InputSocket::check_validity

LinkAddedCallbacksRegistry, LinkBrokenCallbacksRegistry, ValueChangedCallbacksRegistry

qualifier_t -> function pointer

InputSocket / OutputSocket::trigger_*

OperatorInstantiatorsRegistry<DB>

qualifier_t -> Operator<DB>* (*)(const std::string& name)

GraphRuntime<DB>

Rules:

  • An entry is never overwritten: register_entry returns false if the key exists.

  • get(key) throws std::out_of_range; get_safe(key, default) doesn’t.

  • There is no way to unregister: the registries are meant to be filled once, at startup.

Self-registration at static initialization#

The usual pattern registers entries from a .cc file, in an anonymous namespace, by initializing a dummy variable:

namespace {
    bool registered = ng::NodesRegistry::instance().register_entry(MY_QUALIFIER, make_definition());
}

Nothing has to call a “register everything” function: linking the .cc file is enough. Two pitfalls come with it:

  1. Static libraries drop it. If the file is in a static library and nothing references one of its symbols, the linker doesn’t include it and the registration never happens. That’s why the library and the test elements are CMake OBJECT libraries.

  2. Static initialization order. The order of initialization between .cc files is unspecified. A registration must not depend on a global defined in another file. The registries themselves are function-local statics (instance()), so they are always constructed on first use. Qualifiers used as keys should be constexpr (ng::make_qualifier) rather than globals initialized with ng::as_qualifier(...), which run at an unknown time.

Callbacks are referenced by qualifier#

A socket doesn’t store a function pointer but the qualifier of a callback. The function is looked up in the registry each time the callback is triggered. This is what makes callbacks serializable: a saved graph contains qualifiers, and the program reading it provides the functions. A qualifier with no registered function is silently ignored.