Builtin Library
- ..
- README.md7.9 KB
- about.yaml560 B
- behavior.luau29.4 KB
- inspector.luau5.9 KB
- type.yaml2.8 KB
README.md
data (asset type)
A .data instance is a configured value binding to a dataType
contract: values.yaml names the contract and supplies the field
values the contract's schema declares. Instances carry values only —
structure (field types, constraints, defaults) and behavior live on the
bound contract, never on the instance. Think of it as the
ScriptableObject asset to dataType's ScriptableObject class.
When to use one
- You have a
dataTypecontract (e.g.weapon,questObjective) and need one or more concrete, named configurations of it (smg,rifle,sniper). - You want the configuration to persist as authored content, discovered
by
asset.list("data")/contractType:instances(), and validated against the contract on every edit. - Mutable runtime state (ammo remaining, cooldown timers) belongs on
a component, not here — a
.datainstance is configuration, not simulation state.
If you don't have a contract yet, author one first
(asset.create("dataType", ...)).
Where it lives
- Source:
/zero/source/.../<Name>.data/ - Identity:
<Name>(the.datasuffix strips). - Folder shape:
values.yaml—contract: <dataTypeName>plus avalues:map of the contract's fields. Required..metadata— description + tags for search. Required.
Play-mode edits fork a runtime copy (runtime_copy: true) — tweaking
values while gameplay runs never writes back to the source instance.
How to create one
asset.create("data", "<Name>", { contract = "<dataTypeName>" })
-- Creates: /zero/source/<Name>.data/
-- values.yaml (contract binding + the contract's declared defaults)
-- .metadata (empty description/tags — fill both to publish)
opts.contract is required. Creating without opts or without
contract fails on the missing value; the error that names the problem
(no dataType contract named "<x>", saying to author the contract first) is the
one for a contract name that isn't a registered dataType. Either way
nothing is written. The seeded values.yaml fills every field
the contract declares a default for, so a fresh instance is immediately
valid wherever every required field has a default.
Ref surface (AssetRef<data>)
-
ref:contract()— the boundAssetRef<dataType>. -
ref:satisfies(wanted)—(ok, reason?): whether this instance's contract chain includeswanted(the bound contract itself, or any contract it extends).reasonis a string explaining afalseresult (unrelated contract, unresolvable binding, invalid values). -
ref:validate()—(ok, violations)against the bound contract's merged schema.violationsis an array of{ path, message }. -
ref:data(wanted?)— the resolved, read-only value object: this instance'svalues.yamlwith the bound contract's schema defaults filled in,assetRef/dataReffields materialized (assetRef→AssetRef,dataRef→ a nested value object resolved the same way), and the bound contract chain'sbehavior.luaumethods reachable by field-style call —v:someMethod()alongsidev.someField. Method resolution is child-first: the bound contract's own methods win over an inherited one of the same name, and either loses to an explicit instance value of that name (values always shadow methods). A contract with nobehavior.luausimply contributes no methods — it still inherits its ancestors' via the chain.Pass
wantedto assert the instance's bound contract satisfies that contract before resolving (same semantics as:satisfies) — it's an assertion of intent, not a filter: the returned object still carries the full bound schema and full method chain, not a view scoped towanted.Raises when the instance's values violate its contract (same violations
:validate()reports), whenwanteddoesn't appear in the bound contract's chain, and whendataReffields form a circular chain (instance A referencing B referencing A).The object is read-only at the top level — assigning a field raises; change configuration by editing
values.yamlinstead. Nested plain tables (struct and array values) are private copies of the resolved snapshot: mutating them changes only your snapshot, never the source instance or any other caller's resolve.Generalized iteration (
for k, v in w do) walks the resolved value fields (explicit values + filled defaults); methods are reachable by name but never appear in iteration.local w = smg:data() -- resolved value object print(w.damage, w.fireMode) -- schema values (with defaults) print(w:dps()) -- a behavior.luau method, if the contract ships one local weapon = smg:data("weapon") -- asserts smg's contract chain includes "weapon"
Discovery
asset.list("data")— every registered instance, asAssetRefs.<contractRef>:instances()— every.datainstance bound to that contract or to a contract that extends it (seedataType's ref surface).asset.inspect("<name>")— the instance's files, bound contract, values, and this type README.ls /zero/source/<Name>.datalists the files the folder holds.
Authoring conventions
- Name instances for the specific thing they configure (
smg,rifle), not for the contract they bind (that'svalues.yaml::contract). - Keep
values.yamllimited to configuration; anything that changes during gameplay belongs on a component instead. - Say what this specific configuration is tuned for in the
descriptionof its.metadata— the contract's own README documents field meaning.
Common pitfalls
- Missing or unknown contract at creation.
opts.contractmust name an existingdataType; a missingopts, a missingcontractand an unknown contract name each error before any file is written. - Values drifting from the contract. Editing
values.yamlre-runs validation immediately; a violation warns rather than silently passing through. - Confusing configuration with runtime state. A
.datainstance never mutates during play — components own live state.
Related types
.dataType— the contract this instance is bound to; owns structure, validation, and (optional) behavior..preset— a named set of values for one declarer's settings schema (an assetType, an importer, a component), not a contract of its own.
In the Inspector
Values: a link to the contract this instance is bound to, every contract field as a typed control (data_schema's toFields), and the values that break the contract. An edit is checked against the contract and written through dataRef:setValues, one undoable step. setValues re-encodes values.yaml with Yaml.encode, so comments in the file are not kept; a values.yaml that carries comments is therefore shown read-only, with Open for the file.
dataRef:setValues(patch) replaces the named top-level fields and checks the result against the contract. A change is refused (one error naming each violation, nothing written) when it introduces a violation or leaves one standing at or under a value it set. A list item is known by what it holds rather than where it sits, so removing or reordering items sets none of the rest. A violation the file already held anywhere else stays listed and does not stop the change, so an instance that does not yet satisfy its contract fills in one field at a time, and a value beside a key the contract does not declare stays editable. It writes values.yaml, recorded into the open undo operation when there is one. An Inspector edit writes only the leaves it changed: every other value keeps its stored value and form, including a colour held as numbers, a map-form vector, and keys the contract does not declare.