Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Component Observers #67

Open
minecrawler opened this issue Jul 11, 2023 · 0 comments
Open

Implement Component Observers #67

minecrawler opened this issue Jul 11, 2023 · 0 comments
Labels
enhancement New feature or request
Milestone

Comments

@minecrawler
Copy link
Collaborator

minecrawler commented Jul 11, 2023

Components' fields change all the time, and sometimes we want to react to a certain change on a component without having complex logic or writing our own event machinery.

The goals are:

  • enable snapshots to capture component values
  • simplify listening to property value changes for components which don't change a lot
  • augment observation so that 3rd party code just works

One example could be listening for changes to an object (as a component) which is managed by a thrid-party library, like a physics engine or AI.

A more understandable example could be the implementation of achievements. Whenever a player enables a lever, the state can be observed and inside a system it can be tracked without having to add new logic. Let's say the game wants to track that a player activated a lever three times:

class Lever {
    constructor(public state: LeverState) {}
}

class LeverInteraction {
    constructor(public target: Lever) {}
}

class LeverInteractionEvent {
    constructor(
        public readonly entity: IEntity,
        public readonly lever: Lever,
        public readonly field: string,
        public readonly oldValue: unknown,
        public readonly newValue: unknown,
    ) {}
}

class LeverState {
    active = false
}

// ...

prepWorld.buildEntity().withComponents(
    new Mesh('lever.glb'),
    Interactive,
    Obervable(Lever, LeverInteractionEvent),
    // ...
).build();

// ...

const LeverSystem = createSystem({
    query: queryComponents({leverInteractions: Read(LeverInteraction)}),
})
    .withRunFunction(({query}) =>
        // Change lever state after activation
        query.execute(({leverInteractions}) => leverInteractions.target.state.active = true)
    )
    .build();

const ActivateLeverThreeTimesSystem = createSystem({
    achievementState: Storage({ activatedLevers: 0, unlocked: false }),
    leverInteractionEvent: ReadEvent(LeverInteractionEvent),
})
    .withRunFunction(({achievementState, leverInteractionEvent}) =>
        leverInteractionEvent.execute(event => {
            // Increment number of lever activations this step
            if (event.newValue as boolean == true) {
                achievementState.activatedLevers++;
            }
        });

        // Check if the threshold was reached
        if (!achievementState.unlocked && achievementState.activatedLevers >= 3) {
            achievementState.unlocked = true;
            // unlock achievement!
        }
    )
    .build();

This code is not final, but should demonstrate how such an observer could be used from a user perspective. The implementation can be done using setters in the Object definition. We will not use Proxys, since their performance is abysmal. Also, observers should be used sparingly, since they augment a function call behind a simple value assignment.

@minecrawler minecrawler added the enhancement New feature or request label Jul 11, 2023
@minecrawler minecrawler added this to the 0.6.3 milestone Jul 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: To do
Development

No branches or pull requests

1 participant