Composition & references
Linking across files
Connections link properties inside one document. Composition links across documents: an element can reference another .prisma/.prism file and pull its subtree in at that point. Model an asset once; reference it many times; fix it in one place and every use updates.
def group "garage" {
def object "car1" { reference "car.prisma" }
def object "car2" { reference "car.prisma" } # the same model, twice
}
The reference directive names another document, and optionally a path inside it: reference "parts.prisma" "/parts/bolt" pulls in just that element. It is its own statement, not a string property, so a reference always reads as a reference. At author time the link is recorded; resolution happens when you ask for the composed document.
In code, reference is a method on the element handle; chain an override straight after it:
doc.append(Path("/garage"), "group");
doc.append(Path("/garage/car1"), "object")
.reference("car.prisma")
.set("color", Float3(0, 0, 1)); // local override wins over the referenced colourdoc.append("/garage", "group")
doc.append("/garage/car1", "object") \
.reference("car.prisma") \
.set("color", (0, 0, 1)) # local override wins over the referenced colourReferencing a set with wildcards
A concrete path names one element, and that element becomes the referencing element. A pattern names a set, and each match arrives as a child of the referencing element, which stays in place. Two wildcards build a pattern:
*matches one path segment, and globs a name within a segment:/parts/*,/parts/bolt_*,/parts/*_lod0.**matches any depth:/parts/**is every descendant of/parts;/world/**/camerais everycameraat any depth under/world.
Pull every part under /parts into one assembly:
def group "hardware" {
reference "parts.prisma" "/parts/*"
}
Composing that gives /hardware/bolt, /hardware/nut, /hardware/washer, each with its own subtree. The /parts group itself stays behind; only what sits under it comes through. ** reaches deeper and brings the elements between the prefix and each match along, so the tree stays whole:
def group "scene" {
reference "rig.prisma" "/rig/**/camera" # every camera under /rig, at any depth
}
The pattern is the same string in code, on both the C++ handle and the Python binding:
doc.append(Path("/hardware"), "group").reference("parts.prisma", "/parts/*");doc.append("/hardware", "group").reference("parts.prisma", "/parts/*")The same glob queries a document you already hold. find returns every element a pattern selects, in document order; edit each one through its path:
for (const Element* cam : doc.find("/world/**/camera"))
doc.edit(cam->path()).set("exposure", Float(1.2));for cam in doc.find("/world/**/camera"):
doc.edit(cam.path).set("exposure", 1.2)Resolving
loadComposed reads a file and flattens every reference it (transitively) names into one document:
Document doc;
doc.loadComposed("garage.prisma"); // resolves references relative to the file on diskdoc = kg.Document()
doc.load_composed("garage.prisma") # resolves references relative to the file on diskFor environments that resolve their own bytes (a server, a sandbox, a bundle), the portable entry point resolves against any resolver you supply, so composition works against in-memory documents just as well:
Document flat = compose(doc, Path("/garage"), [&](std::string ref) -> std::optional<Document> {
return lookUpSomehow(ref); // you decide where bytes come from
});
From the CLI:
kinogaki compose garage.prisma > flat.prisma # references resolved, one Document
Local opinions win
A referencing element can override properties of what it pulls in: local values take precedence over referenced ones. That is what makes shared assets practical. Reference the standard car, then override its colour on car2 without forking the file.
A safe, memoized DAG
References can be transitive (a referenced document may reference others), forming a directed acyclic graph. Resolution memoizes each document, so a diamond (two elements referencing the same file) loads it once rather than exponentially. Cycles are rejected, and a missing reference is a clean, located error.
Why it matters
Linking instead of copying is what turns a folder of files into a connected project, and what lets a program or an agent edit one shared file instead of every copy. "Swap the shared material to the blue one" is a single edit to one referenced document, not a search-and-replace across dozens of files that might miss one. Connections and composition are the same idea at two scales: identity is a path, and a link is just another path, within a document or across them. That principle is also what makes a directory tree expressible as a Document, which is the basis of bundles.