Describe the problem this feature solves
Rust-like enum components would be a nice win.
Potential Solutions
I've listed out a few options here - currently leaning towards (3) as the most likely ideal approach
Solution 1 (Association Enums)
Each enum variant is its own class and the enum type is a union of the variants. We can "associate" these distinct classes as enums by setting a custom static property and reading it to determine component type (as opposed to just looking at object.constructor).
// Defining
class Pending {}
class Resolved {
value: any;
}
class Rejected {
error: any;
}
type Promiseish = Pending | Resolved | Rejected;
// This function mutates these classes to make them associated
const Promiseish = Enum({ Pending, Resolved, Rejected });
const instance = new Promiseish.Pending();
// In use
function mySystem(query: Query<Promiseish>) {
for (const promiseish of query) {
console.log(typeof promiseish); // Could be Promiseish or any subtype!
}
}
Pros:
- Localizes enum definition to one place
- Pretty small API
- Enums look like normal components mostly
- Direct handling of enum instances
Cons:
- Must define enums twice (once as a type, once as an object)
Enum() mutates the classes given to it
- Columns will store objects of variable shape, queries could deopt because promiseish is multiple kinds of object
Solution 2 (Wrapper Enums)
Enums as wrappers with a type and data field:
class Promiseish extends Enum({ Pending, Resolved, Rejected }) {}
const instance = new Promiseish(new Pending());
instance.type; // Pending
instance.data; // new Pending() created above
Pros:
Cons:
- Twice as many objects
- Don't get to handle enum objects directly (i.e. always thru
.data)
- Kind of curious how VMs handle objects with references to other objects with different shapes - no idea on what this looks like for perf, if it even matters.
Solution 3 (Extension Enums)
This isn't exactly enums but it achieves a similar result. Rework how archetypes are modeled (we probably have to do this anyway) and allow filters/modifiers to define a matching function. Expose an Extended<T> type (would come up with a better name) that matches subclasses and not just the exact class. This could have uses beyond just enum-like behavior or the Extended<T> type
// Defining
class Promiseish {}
class Pending extends Promiseish {}
class Resolved extends Promiseish {
data: any;
}
class Rejected extends Promiseish {
error: string;
}
// In use
function mySystem(query: Query<Extended<Promiseish>>) {
for (const promiseish of query) {
console.log(promiseish.constructor); // Could be Promiseish or any subtype!
}
}
Pros:
- Direct object handling
- Single definition of "union" type
- Table columns are single-shape
- Extensible enums! e.g. a physics library could provide a
Collider type and just require consumers to extend the class with an onCollide() method. Library systems can then query for consumer components
Cons:
- Changing enum type is a table move (does this happen much?)
- Always have to specify
Extended<T> in queries (we'd make this shorter, but still)
- Queries still have de-opt potential
- Extensible enums (hah)
Describe the problem this feature solves
Rust-like enum components would be a nice win.
Potential Solutions
I've listed out a few options here - currently leaning towards (3) as the most likely ideal approach
Solution 1 (Association Enums)
Each enum variant is its own class and the enum type is a union of the variants. We can "associate" these distinct classes as enums by setting a custom static property and reading it to determine component type (as opposed to just looking at
object.constructor).Pros:
Cons:
Enum()mutates the classes given to itSolution 2 (Wrapper Enums)
Enums as wrappers with a
typeanddatafield:Pros:
Cons:
.data)Solution 3 (Extension Enums)
This isn't exactly enums but it achieves a similar result. Rework how archetypes are modeled (we probably have to do this anyway) and allow filters/modifiers to define a matching function. Expose an
Extended<T>type (would come up with a better name) that matches subclasses and not just the exact class. This could have uses beyond just enum-like behavior or theExtended<T>typePros:
Collidertype and just require consumers to extend the class with anonCollide()method. Library systems can then query for consumer componentsCons:
Extended<T>in queries (we'd make this shorter, but still)