Reactive Programming
Easel is a reactive programming language. It helps to understand this so you can write code that works with the engine rather than against it.
What is Reactive Programming?
Reactive programming is a programming paradigm where you say what code depends on what state. Then, when the state changes, the engine automatically re-runs the code that depends on that state. Only the code that depends on the changed parts of the state will be re-run, so it is very efficient.
Reactive programming is used most often with user interface frameworks like React. In games, it has a lot of similarities to the idea of immediate mode rendering, for example used by Dear ImGui. Easel brings the concept of reactive programming to all of game development.
A minimal example
Let say you have a spaceship, and you want your spaceship to become more red as it takes damage. This is how you would do it in Easel, in a reactive way:
with Health {
ImageSprite(image=, radius=, color=(Health / MaxHealth).Mix(#f00, #fff))
}
The with block is what makes this code reactive.
The code block runs once, and then every time the Health changes,
allowing the sprite's color and the Health to stay in sync.
Think about state, not output
A common trap new Easel developers fall into is, they just wish they could, for example, change the sprite's color to red.
Why isn't there a SpriteColor property that I can just set to red? Why would a game engine not have this?
The pitfall is, if you are thinking about editing your sprite directly,
you are thinking too much about the output, and not enough about the state.
In what situation does your sprite become red?
Is it when your spaceship's health is low? Is it when you're holding a ruby? Is it when your character is angry?
Once you know state it depends on,
you can use a with block to make the sprite's color depend on that state.
Let's say you now understand that you want your sprite to be red when your character is angry. Now you have a decision to make about how you represent this.
- Is it a boolean flag?
pub prop unit.IsAngry = false - Is it a magnitude?
pub prop unit.Angriness = 100? - Is anger one of many possible emotions?
pub prop unit.Emotion = Emotion:Angry - Maybe other entities give you anger, and so it's a collector?
pub collector unit.Anger ~ Sum
Let's say anger is one of the many emotions. Now we can write a with block to link the sprite's color to the Emotion:
pub prop unit.Emotion = Emotion:Angry
pub symbol Emotion:Happy
pub symbol Emotion:Sad
pub symbol Emotion:Angry
pub fn unit.Dog() {
// ...
with Emotion {
let color = match Emotion {
Emotion:Happy => #ff0,
Emotion:Sad => #00f,
Emotion:Angry => #f00,
}
ImageSprite(image=, radius=, color=)
}
}
Think about the state first. Get that right, and then the output will follow naturally.
Reactivity and performance
Easel is designed around a one-way transformation from state to output. For that reason, unlike other game engines, you can't read back or edit any properties of a sprite once you've created it. No reading back its color, size, shape, etc.
This allows the engine to become even more optimized. When you make a sprite in Easel, it gets converted into vertices ready for rendering. This loses the original inputs that you used to create the sprite, but it allows the engine to render the sprite much faster. Easel relies on the fact that your game state is the source of truth, and so if you need to edit the sprite, you are already storing everything necessary to re-create the sprite with the new properties.