Skip to main content

IDs

In Easel, an entity can only have multiple instances of the same type of component if they have different IDs. The ID can be specified using the FunctionName<Id> syntax. IDs can be specified manually, but they are often automatically generated by the compiler and so you normally don't need to think about them except in special cases.

Sometimes the word Discriminator is used to more specifically refer to the same concept, as ID is a fairly generic word that can mean many things.

In code, IDs are often written as Id to match Pascal case conventions.

pub fn this.Example() {
// Two different ImageSprites with different IDs
ImageSprite<a>(image=@spiral.svg, radius=5)
ImageSprite<b>(image=@spiral.svg, radius=10)

await Tick(5s)

// Replace the ImageSprite with the ID <b>
ImageSprite<b>(image=@spiral.svg, radius=15)
}

Forwarding IDs

The main purpose of IDs is to be forwarded to another function. This is useful when a function with an ID wants to be able to identify its own components, without getting mixed up with the components created by other invocations of the same function.

pub fn this.DoSomethingCool<Id>(radius) {
// Make sure our ImageSprite is ours by giving it our ID
ImageSprite<Id>(image=@spiral.svg, radius=)

await Tick(5s)

delete ImageSprite<Id>
}

// The function below is actually sharing the same ID for all calls to ThisWouldNotWork,
// meaning different calls with different IDs interfere with each other
// which is probably not what you want.
pub fn this.ThisWouldNotWork<Id>(radius) {
// Different calls to ThisWouldNotWork use the same ImageSprite ID,
// so they interfere
ImageSprite<abc>(image=@spiral.svg, radius=)

await Tick(5s)

delete ImageSprite<abc>
}

The plus (+) sign can be used to concatenate symbols and create a new sub-ID. This is useful when one function with an ID needs to manage its own multiple subcomponents, without getting mixed up with the components created by other invocations of the same function.

pub fn this.DoSomethingCool<Id>(radius) {
ImageSprite<Id+a>(image=@spiral.svg, radius=)
ImageSprite<Id+b>(image=@dodecahedron.svg, radius=)

await Tick(5s)

delete ImageSprite<Id+a>
delete ImageSprite<Id+b>
}

Automatic IDs

Specifing <Id = auto> in the function signature, where Id is the name of your ID, will make the Easel compiler generate an ID for you, so that you don't have to do it most of the time.

This is useful for functions like ImageSprite, where it is makes sense for an entity to have multiple instances of the same component. If you look at the documentation for ImageSprite, you will see it has <Id = auto>, making it easy to have two ImageSprites without them replacing each other.

pub fn this.Example() {
// Two different ImageSprites with different IDs
// implicitly generated by the compiler.
// This happens because ImageSprite is declared with <Id=auto> (see its documentation)
ImageSprite(image=@spiral.svg, radius=5) // like ImageSprite<auto1>(...)
ImageSprite(image=@spiral.svg, radius=10) // like ImageSprite<auto2>(...)
}

When using automatic IDs, a unique ID is assigned to each line of code. This means that calling the same block of code in a loop will replace the previous instance.

pub fn this.Example() {
// This loop replaces the same ImageSprite with a bigger one every 1 second.
for i in Range(0,10) {
// This ImageSprite gets replaced each time the loop repeats
// because it is the same line of code and therefore has the same automatic ID each time.
ImageSprite(image=@spiral.svg, radius=10+i) // like ImageSprite<auto123>(...)
await Tick(1s)
}
}

Automatic IDs are automatically concatenated with the parent ID. That means that any components you create will by default be kept separate from other components created by different invocations of the same function, without you needing to worry about it!

pub fn this.Spiral<Id>(radius) {
// Similar to writing ImageSprite<Id+auto123>(...)
// where `auto123` is an example of what an automatically-generated ID
ImageSprite(image="spiral.svg", radius=)
}
pub fn this.Example() {
// The ImageSprites created by these two Spiral calls
// do not interfere with each other
Spiral<a>(radius=5) // Creates an ImageSprite<a+auto123>
Spiral<b>(radius=10) // Creates an ImageSprite<b+auto123>
}

File-level and global IDs

If an ID is specified beginning with a lowercase letter, then it is a declared at the file level. This allows other functions in the same file can refer to the same ID. Functions in other files cannot use the same ID and would instead be using their own independent file-level ID.

// file1.easel
pub fn this.SameFileExample1() {
ImageSprite<small>(image=@spiral.svg, radius=5)
ImageSprite<large>(image=@spiral.svg, radius=10)
}
pub fn this.SameFileExample2() {
// This is in the same file, so this would replace the ImageSprite<large> created by SameFileExample1
ImageSprite<large>(image=@spiral.svg, radius=15)
}

// file2.easel
pub fn this.DifferentFileExample() {
// This is in a different file, so this would create a new independent ImageSprite<large>
ImageSprite<large>(image=@spiral.svg, radius=15)
}

If an ID is specified beginning with an uppercase letter, then the compiler will find the symbol in the global scope, and use it as a global ID.

// spiral.easel
pub symbol Fred // declare a globally-unique symbol named Fred

// file1.easel
pub fn this.File1Example() {
ImageSprite<Fred>(image=@spiral.svg, radius=10)
}

// file2.easel
pub fn this.File2Example() {
// Even though this is in a different file, 'Fred' refers to the same symbol,
// and so this would replace the Spiral<Fred> created by File1Example
ImageSprite<Fred>(image=@spiral.svg, radius=15)
}

Optional IDs

An ID may be marked as optional by using the ? suffix. Note, this doesn't mean that the function doesn't have an ID, it just means that it defaults to null, which means all instances of the function will share the same ID unless specified otherwise. This is suitable for functions like PolygonCollider where it makes sense to normally only have one instance of the component.

// '?' suffix indicates that Id is optional
pub fn this.Spiral<Id?>(radius) {
ImageSprite<Id>(image="spiral.svg", radius=)
}

pub fn this.Example() {
Spiral(radius=5)
Spiral(radius=7) // this replaces the Spiral from the previous line because they both have no ID

Spiral<large>(radius=10) // separate ID, so does not replace previous Spiral
}