Package

All Ambient packages must have an ambient.toml manifest that describes their functionality. This format is in flux, but is inspired by Rust’s Cargo.toml.

Next to the ambient.toml, other files may be present. A screenshot.png can be used to provide a thumbnail for the package on the Ambient website. A README.md can be used to provide a description of the package on the Ambient website.

To view documentation for a package, add --open-docs to a command that builds packages (i.e. ambient build/run/serve/...). This documentation is autogenerated and contains all items available to that package.

Package definitions are “projected” to guest code, so that they can use them. For Rust, this is done through the use of a build script that generates a src/packages.rs, creating a packages module that contains all the packages known to the package, including itself. Your own package can be accessed through packages::this.

Reference

  • SnakeCaseIdentifiers are snake-case ASCII identifiers (as a string)
  • PascalCaseIdentifiers are PascalCase ASCII identifiers (as a string)
  • Identifiers are either a SnakeCaseIdentifier or a PascalCaseIdentifier based on context
  • ItemPaths are a double-colon-separated list of SnakeCaseIdentifiers followed by a single Identifier. For example, my_package is an Identifier, and my_package::my_component is an ItemPath.
  • See ValueType for a description of the types that can be used in Ambient.

Package / [package]

The package section contains metadata about the package itself, such as its name and version.

PropertyTypeRequiredDescription
idSnakeCaseIdentifierThe package’s ID. Autogenerated by Ambient. Do not attempt to specify your own ID.
nameStringA human-readable name for the package.
versionStringThe package’s version, in (major, minor, patch) format. Semantically versioned.
contentPackageContentA description of the content of this Package. See below.
ambient_versionStringThe version of Ambient this package is intended to be used with.
authorsString[]The authors of the package.
descriptionStringA human-readable description of the package.
repositoryStringWhere the source code for this package can be found.
publicBoolIndicates if this package will be publicly available when deployed. Defaults to true.

PackageContent

These are the valid configurations for package content:

# A Playable is anything that can be run as an application; i.e. games, examples, applications etc.
content = { type = "Playable" }
content = { type = "Playable", example = true } # example defaults to false

# Assets are things you can use as a dependency in your package
content = { type = "Asset", models = true, textures = true } # Contains models and textures
# These are the valid asset types:
#
#   models
#   animations
#   textures
#   materials
#   audio
#   fonts
#   code
#   schema
#
# You can use any combination of them

# Tools are things you can use to develop your package
content = { type = "Tool" }

# Mods are extension to Playables
content = { type = "Mod", for_playables = ["i3terk32jw"] }

Example

#
# The package section describes all package metadata.
#
[package]
id = "d563xtcr72ovuuhfkvsgag6z3wiy5jwr"
name = "My Cool Package"
version = "0.0.1"
content = { type = "Asset", code = true }
ambient_version = "0.3.0"

# The following are optional:
# authors = ["Cool Cat"]
# description = "A sample package that's the coolest thing ever."
# repository = "https://my-cool-forge.io/my-cool-package"
# public = true

Build / [build]

The build section contains settings related to building the package.

Rust Settings / [build.rust]

PropertyTypeRequiredDescription
feature-multibuildString[]An array of strings defining the Rust features to be used when building the package. This is used to build the same code for both client and server.

cargo build will be run with each of these features to produce a separate WASM binary, which is then componentized and copied into a folder of the corresponding name in build/.

Client and server are built by default (e.g. ["client", "server"]); this is exposed so that you can disable building one side entirely if required.

Example

[build.rust]
feature-multibuild = ["client", "server"]

Components / [components]

The components section contains custom components defined by the package. Components are used to store data on entities.

This is a TOML table, where the keys are the component IDs (SnakeCaseIdentifier), and the values are the component definitions.

PropertyTypeRequiredDescription
typeValueTypeThe type of the component.
nameStringA human-readable name for the component.
descriptionStringA human-readable description of the component.
attributesComponentAttribute[]An array of attributes for the component.

A ComponentAttribute is a string that can be one of the following:

  • Debuggable: this component can have its debug value printed, especially in ECS dumps
  • Networked: this component is networked
  • Resource: this component will only ever be used as a resource; will error if attached to an entity
  • MaybeResource: this component can be used as a resource or as a component; necessary if treating this component as a resource
  • Store: this component’s value should be persisted when the world is saved

Example

[components]
# Inline tables can be used.
cool_component = { type = "I32", name = "Cool Component", description = "A cool component", attributes = ["Debuggable"] }

# Explicit tables can also be used.
[components.cool_component2]
type = "I32"
name = "Cool Component 2"
description = "A cool component 2"
attributes = ["Debuggable"]

Concepts / [concepts]

The concepts section contains custom concepts defined by the package. Concepts are used to define a set of components that can be attached to an entity. For more information on how to use concepts, see the ECS documentation.

This is a TOML table, where the keys are the concept IDs (CamelCaseIdentifier), and the values are the concept definitions.

PropertyTypeRequiredDescription
nameStringA human-readable name for the concept.
descriptionStringA human-readable description of the concept.
extendsString[]An array of concepts to extend. Must be defined in this package manifest.
components.requiredMap<ItemPath, ConceptValue>An object containing the required components for this concept, and any associated information about the use of the component in this concept (see below).
components.optionalMap<ItemPath, ConceptValue>An object containing the optional components for this concept, and any associated information about the use of the component in this concept (see below). These components do not need to be specified to satisfy a concept, but may provide additional control or information if available.

The components is an object where the keys are ItemPaths of components defined in the package manifest, and the values are ConceptValues.

ConceptValues are a TOML table with the following properties:

PropertyTypeRequiredDescription
descriptionStringA human-readable description of the component in the context of the concept, which may be different to the component’s description.
suggestedtoml::ValueIf specified, the suggested value for this component in this concept. This is merely a suggestion, but must match the type of the component.

Mat4 and Quat support Identity as a string, which will use the relevant identity value for that type.

F32 and F64 support PI, FRAC_PI_2, -PI, and -FRAC_PI_2 as string values, which correspond to pi (~3.14), half-pi (~1.57), and negative versions respectively.

Example

[concepts.Concept1]
name = "Concept 1"
description = "The best"
[concepts.Concept1.components.required]
cool_component = {}

# A concept that extends `concept1` and has both `cool_component` and `cool_component2`.
[concepts.Concept2]
extends = ["Concept1"]

[concepts.Concept2.components.required]
cool_component2 = { suggested = 42 }

[concepts.Concept2.components.optional]
cool_component3 = { suggested = 42 }

Messages / [messages]

The messages section contains custom messages defined by the package. Messages are used to communicate between client and server, or between packages/modules on the same side.

For an example of how to use messages, see the messaging example.

This is a TOML table, where the keys are the message IDs (PascalCaseIdentifier), and the values are the message definitions.

PropertyTypeRequiredDescription
descriptionStringA human-readable description of the message.
fieldsMap<SnakeCaseIdentifier, ValueType>An object containing the fields and their types. Must be one of the types supported for components.

Example

[messages.Input]
description = "Describes the input state of the player."
[messages.Input.fields]
# Each field in the message must have a type.
direction = "Vec2"
mouse_delta_x = "F32"

Enums / [enums]

The enums section contains custom enums defined by the package. Enums are used to define a closed set of values.

This is a TOML table, where the keys are the package IDs (PascalCaseIdentifier), and the values are the package definitions.

PropertyTypeRequiredDescription
descriptionStringA human-readable description of the enum.
membersMap<PascalCaseIdentifier, String>An object containing the members and their descriptions. The description can be empty.

Example

[enums.CakeBakeState]
description = "Describes the state of a cake bake."
[enums.CakeBakeState.members]
GatheringIngredients = "Gathering ingredients"
MixingIngredients = "Mixing ingredients"
Baking = "Baking"
Cooling = "Cooling"
Decorating = "Decorating"
Done = "Done"

Includes / [includes]

The includes section contains a list of manifests to pull in under a given name. This is useful for splitting up a package into multiple files.

This is a TOML table, where the keys are the name that you want to access this include by (SnakeCaseIdentifier), and the location of the package manifest is the value.

Example

[includes]
graphics = "graphics/ambient.toml"

Dependencies / [dependencies]

The dependencies section contains a list of package IDs that this package depends on.

Depending on another package gives you access to its items, including its components, concepts, messages, and enums. It can also provide access to any assets that the package has.

This is a TOML table, where the keys are the name that you want to access this package by (SnakeCaseIdentifier), and the location of the package is the value.

To access an item from a package, use the following syntax: import_name::item_id. For example, if you have a package imported with the name the_basics and an enum with ID BasicEnum, you can access it with the_basics::BasicEnum.

At least one of path or (id and version) must be specified.

PropertyTypeDescription
pathStringA relative path to the package to depend on.
idStringThe ID of a package to depend on. Must be combined with version.
versionStringThe version of a package to depend on. Only exact versions are currently supported. Must be combined with id.
enabledboolControl whether or not logic associated with this package should be enabled on load. Enabled by default.

For an example of how to use dependencies, see the dependencies example.

Example

[dependencies]
the_basics = { path = "../basics" }

[components]
my_component = { type = "the_basics::BasicEnum" }

Runtime access to packages

Packages are represented as entities within the ECS, with their metadata being stored as components. This means that you can access the metadata of a package at runtime. To do so, you can use the entity() function inside the generated Rust code for the package:

use ambient_api::prelude::*;

#[main]
fn main() {
    dbg!(entity::get_all_components(packages::this::entity()));
}

Or by querying for entities that have the is_package component:

use ambient_api::{
    core::package::components::{is_package, name},
    prelude::*,
};

#[main]
fn main() {
    let q = query((is_package(), name())).build();
    // List all packages and their names.
    dbg!(q.evaluate());
}

ValueType

In Ambient, all typed values must have a type that belongs to ValueType. This includes component types and message fields.

A ValueType is either:

  • a string that can be one of the following primitive types:

    • Bool: a boolean value, true or false
    • Empty: a component that has no value; most often used for tagging an entity
    • EntityId: an entity ID
    • F32: a 32-bit floating point value
    • F64: a 64-bit floating point value
    • Mat4: a 4x4 32-bit floating point matrix
    • Quat: a 32-bit floating point quaternion
    • String: a UTF-8 string
    • U8: an 8-bit unsigned integer value
    • U16: an 16-bit unsigned integer value
    • U32: a 32-bit unsigned integer value
    • U64: a 64-bit unsigned integer value
    • I8: an 8-bit signed integer value
    • I16: an 16-bit signed integer value
    • I32: a 32-bit signed integer value
    • I64: a 64-bit signed integer value
    • Uvec2: a 2-element 32-bit unsigned integer vector
    • Uvec3: a 3-element 32-bit unsigned integer vector
    • Uvec4: a 4-element 32-bit unsigned integer vector
    • Ivec2: a 2-element 32-bit signed integer vector
    • Ivec3: a 3-element 32-bit signed integer vector
    • Ivec4: a 4-element 32-bit signed integer vector
    • Vec2: a 2-element 32-bit floating point vector
    • Vec3: a 3-element 32-bit floating point vector
    • Vec4: a 4-element 32-bit floating point vector
    • Duration: A time span. Often used as a timestamp, in which case it designates the duration since Jan 1, 1970.
  • a contained type of the form { type = "Vec", element_type = ValueType } or { type = "Option", element_type = ValueType }

    • Note that Vec and Option are the only supported container types, and element_type must be a primitive ValueType (that is, you cannot have nested contained types).
  • a string that refers to an enum defined by a package; see Enums.

Note that ValueTypes are not themselves values, but rather types of values. For example, Vec2 is a ValueType, but Vec2(1.0, 2.0) is a value of type Vec2. Additionally, ValueTypes from other packages can be referred to using ItemPaths: my_package::my_component::MyType.

WebAssembly

All .wasm components in the build/{client, server} directory will be loaded for the given network side. The .wasm filenames must be snake-case ASCII identifiers, like the id in the manifest.

This means any .wasm which implements the Ambient WIT interface and targets WASI snapshot 2 (or uses an adapter that targets WASI snapshot 2) should run within Ambient.

As a convenience for Rust users, Ambient will automatically build a Cargo.toml if present at the root of your package, as wasm32-wasi for the features specified in build.rust.feature-multibuild in ambient.toml (defaults to client and server).

The default new package template will create client.rs and server.rs files, with a Cargo.toml preconfigured with targets for both. The resulting WASM bytecode files are then converted to a component and placed in build/{client, server}.

The process it takes is equivalent to these commands:

cd your_package
cargo build --target wasm32-wasi --features client
wasm-tools component new target/wasm32-wasi/debug/your_package_client.wasm -o build/client/your_package.wasm --adapt wasi_snapshot_preview1.command.wasm

cargo build --target wasm32-wasi --features server
wasm-tools component new target/wasm32-wasi/debug/your_package_server.wasm -o build/server/your_package.wasm --adapt wasi_snapshot_preview1.command.wasm

using wasm-tools and a bundled version of the preview1-to-preview2 WASI adapter.

Rust

Rust is a first-class language for Ambient packages. The default new package template will create client.rs and server.rs files, with a Cargo.toml preconfigured with targets for both.

The API provides a #[main] attribute macro that generates code to allow you to access the data and functionality of the packages known to your package. All packages, including your own, will be in the packages module.

Restrictions

When running locally, guest code can:

  • use WASI filesystem APIs (e.g. std::fs in Rust) to read and write files in the data directory of the built package
  • use the Ambient HTTP APIs (e.g. http in Rust) to make HTTP GET/POST requests to arbitrary servers

This functionality is disabled when the server is running on a hosted environment (i.e. Ambient deployments) for security reasons. To test if your logic still works in a hosted environment, run Ambient with the AMBIENT_HOSTED environment variable set to anything (e.g. AMBIENT_HOSTED=1 ambient run).