Skip to content

Conversation

@jkb0o
Copy link
Collaborator

@jkb0o jkb0o commented Oct 22, 2023

This PR is abut to add Signals and Hands to Polako. See hands example.

Signals

Signal is a wrapper around Event associated with Entity. There are user defined signals like TakeDamageSignal or system-provided signals like OnDemand<EnterSignal> or OnDemand<UpdateSignal>.

I can define Signal like this:

#[derive(Signal)]
pub struct TakeDamageSignal {
  entity: Entity,
  damage: f32
}

I can assign the Signal to the Element like this:

#[derive(Element)]
#[signal(take_damage: TakeDamageSignal)]
pub struct Enemy {
  health: f32
}

I can connect to Signal like this:

impl ElementBuilder for Div {
    fn build_element(content: Vec<Entity>) -> Blueprint<Self> {
        blueprint! {
            Enemy::Base { .on.take_damage: (e) => { this.health -= e.damage; } }
            [[ content ]]
        }
    }
}
  • .on.take_damage: defines the signal to connect
  • (e) => { this.health -= e.damage; } is the Hand.
    I can emit event by sending the Signal to EventWriter, or by calling enemy.take_damage.emit(.damage: 23.) from the Hand.

Hands

Hand is the Signal's handler. It looks like closure, but it is not. Hand is parsed manually. Hand have access to eml context and its marks. With this knowledge it is possible to build System with only required params for the exact Hand. For example:

#[derive(Element)]
#[construct(Div -> Empty)]
pub struct Div {
  bg: Color
}

#[derive(Behavior)]
pub struct UiText {
  text: String
}

#[derive(Element)]
#[construct(Label -> UiText -> Div)]
pub struct Label;

commands.add!(eml! {
    resource(time, Time);
    Body {
        .on.update: (e) => {
            delta.text = e.delta.fmt("Frame time: {:0.4}");
            elapsed.text = time.elapsed.fmt("Elapsed time: {:0.2}");
            elapsed.bg.g = (time.elapsed - 2.) * 0.5;
        },
    } [
        Column [
            delta: Label { .text: "0.0000" },
            elapsed: Label { .text: "0.00" },
        ]
    ]
});

Expanded eml! will generate variables time: ResourceMark<Time>, delta: EntityMark<Label> and elapsed: EntityMark<Label>. EntityMark is just an Entity with PhantomData.

The expanded eml will also generate a system-like closure that requests only required system params for this hand. It looks something like this:

let __ext__ =  <<Body as Construct>::Design as Singleton>::instance().on().update();
__ext__.assign(&mut __entity__, move |
    e: &_,                  // infers as &UpdateSignal
    _params: &mut StaticSystemParam<(
        Commands, ParamSet<(
            Query<&mut _>,  // infers as Query<&mut UiText>
            Res<_>,         // infers as Res<Time>
            Query<&mut _>,  // infers as Query<&mut Div>
        )> 
    ,)>
|{
    let (_commands,_params) =  ::std::ops::DerefMut::deref_mut(_params);
    
    // read all required values
    let _v_time_elapsed = {
        let _host = _params.p1();
        time.getters().elapsed(&_host).into_value().get()
    };
    let _v_delta_text = {
        let _inset = _params.p0();
        let _mut = _inset.get(delta.entity).unwrap();
        let _host =  &_mut;
        delta.getters().text(&_host).into_value().get()
    };
    let _v_e_delta = {
        let _host = e;
        e.getters().delta(&_host).into_value().get()
    };
    let _v_elapsed_text = {
        let _inset = _params.p0();
        let _mut = _inset.get(elapsed.entity).unwrap();
        let _host =  &_mut;
        elapsed.getters().text(&_host).into_value().get()
    };
    let _v_elapsed_bg_g = {
        let _inset = _params.p2();
        let _mut = _inset.get(elapsed.entity).unwrap();
        let _host =  &_mut;
        elapsed.getters().bg(&_host).g().into_value().get()
    };
    
    // apply changes
    
    {
        let _val = ({
            let res = format!("Frame time: {:0.4}",_v_e_delta);
            res
        }).into();
        if _v_delta_text!=_val {
            delta.setters().set_text(_params.p0().get_mut(delta.entity).unwrap().as_mut(),_val);
            delta.descriptor().text().notify_changed(_commands, delta.entity);
        }
    };
    {
        let _val = ({
            let res = format!("Elapsed time: {:0.2}",_v_time_elapsed);
            res
        }).into();
        if _v_elapsed_text != _val {
            elapsed.setters().set_text(_params.p0().get_mut(elapsed.entity).unwrap().as_mut(),_val);
            elapsed.descriptor().text().notify_changed(_commands, elapsed.entity);
        }
    };
    {
        let _val = ((_v_time_elapsed-2.)*0.5).into();
        if _v_elapsed_bg_g != _val {
            elapsed.setters().bg(_params.p2().get_mut(elapsed.entity).unwrap().as_mut()).set_g(_val);
            elapsed.descriptor().bg().notify_changed(_commands, elapsed.entity);
        }
    };
});

While this System is suboptimal, there is a lot of space for optimisations.

On-demand Signals

There are two on-demand signals right now: EnterSignal & UpdateSignal. Emitting this signal on every entity is ineffective and useless. So I want to emit this signals only on entities with handles. So when I connect to .on.enter or .on.update - the signals start to emit on the connected entities.

Hand syntax.

Hands are supposed to be very simple blocks of code. So the syntax right now is very limited.

Statements (STMT):

  • assign: mark.prop = EXPR;
  • emit signal: mark.signal.emit(ARGS);
  • send event: MyEvent::send(ARGS);|send(MyEvent[, ARGS]);
  • if: if EXPR { STMT+ } [ elif EXPR { STMT+ } ]* [ else { STMT+ } ]

Expressions (EXPR):

  • const: LITERAL
  • read: mark.prop
  • format: EXPR.fmt(STR)
  • group: (EXPR)
  • add: EXPR + EXPR
  • sub: EXPR - EXPR
  • mul: EXPR * EXPR
  • div: EXPR / EXPR

Arguments (ARGS):

  • empty:
  • single: .name: EXPR
  • multi: .name: EXPR [, .name: EXPR]*

Status

  • Derive signal
  • Assign signal to Element/Behavior
  • On-demand signals
  • .on.signal: param extension
  • statements
    • assign: mark.prop = EXPR
    • emit signal
    • send event
    • if
  • expressions:
    • const
    • read
    • enum
    • construct
    • format
    • group
    • add/sub/mul/div
    • gt/gte/lt/lte
    • and/or
    • eq/ne
    • neg/not

@jkb0o jkb0o merged commit eede58d into master Jan 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants