How do you implement your own listenable in Verse?

I can’t figure out how to implement my own listenable function (Can’t find a code sample on this)

Basically I have a timer and on each loop iteration I want to call OnTimerIteration so anyone who is subscribed can get notified.

I’ve tried every syntax combination I can think of based on the cryptic Verse.digest notes with no luck.

counter := class():
    TimerTickPeriod : float
    var RemainingTime<private> : float = 1.0
    CountdownStartedEvent : event() = event(){}
    CoundownEndedEvent : event() = event(){}
    OnTimerIteration<public> : listenable() # <-- The object one would .Subscribe to
    StartTimer<public>(TotalTime : float): void=
        set RemainingTime = TotalTime
        spawn:
            RunCountdown()
    RunCountdown<private>()<suspends> : void=
        CountdownStartedEvent.Signal()
        loop:
            Sleep(TimerTickPeriod)
            OnTimerIteration() # <-- Let my subscribers know
            set RemainingTime -= TimerTickPeriod
            if (RemainingTime <= 0.0):
                CoundownEndedEvent.Signal()
                break
3 Likes

This is something I ran into myself, sadly don’t have a solution. Do you have any progress?

I want to know this too, I tried a pile of things as well but nothing worked. I think I saw on discord somewhere that it isn’t implemented yet? Not sure though.

2 Likes

Hey @DiG3! Help us out if you’ve got any insight in this topic, please :slight_smile:

Hello,
you can’t initialize a listenable interface inside a class.
For now, I recommend exposing an event like the CountdownEndedEvent (for example OnTimerIteration/OnTick) that lets users of the class Await it. You’d then Signal the event when needed.

I haven’t played around with it much, but maybe something like this could also be helpful to you:

# In this case the functions we accept should take one string parameter and return void.
my_fun := type{_(:string):void}

my_subscribable := class:
    var Funs<private>:[]my_fun = array{}
    
    Subscribe<public>(Fun:my_fun):void =
        set Funs += array{Fun}
    
    Dispatch<private>():void =
        for (Fun:Funs):
            Fun("Hello")
5 Likes

o/ Is there a way to implement custom subcribable events with generics?
Ideally You’d want my_subscribable use subscribable and signalable, be parametric type passing t:type to Funs as you do.
But seems we are hitting dead end of “sane” here with mutables not implemented in parametric types, so how would one go about using those interfaces in custom events?

By “sane” dead end I mean relatively not way-arounds approach, for example user Ep8Script#8819 posted this:

my_callback<internal>(t:type) := class:
    Callback<internal> : t -> void
    SpawnAwait<internal> : type{_(:t->void)<suspends>:void}

    OnSignal(Agent:?agent) : void =
        spawn {SpawnAwait(Callback)}

my_cancelable<internal> := class(cancelable):
    MaybeCancelable<internal> : ?cancelable = false

    Cancel<override>()<transacts> : void =
        if (Cancelable := MaybeCancelable?):
            Cancelable.Cancel()

my_subscribable<internal>(t:type) := class(listenable(t), signalable(t)):
    Event<private> : event(t) = event(t){}

    Await<override>()<suspends> : t =
        Event.Await()

    GetProxyTrigger<private>()<transacts> : ?trigger_device =
        var ProxyTrigger : ?trigger_device = false
        for (Actor : GetCreativeObjectsWithTag(subscribable_proxytrigger{})):
            if (Trigger := trigger_device[Actor]):
                set ProxyTrigger = option{Trigger}
        ProxyTrigger

    Signal<override>(Val:t) : void =
        if (Trigger := GetProxyTrigger()?):
            Trigger.Trigger()
        spawn {SleepSignal(Val)}

    SleepSignal<private>(Val:t)<suspends> : void =
        Sleep(0.0)
        Event.Signal(Val)

    SpawnAwait<private>(Callback:t -> void)<suspends> : void =
        Callback(Await())

    Subscribe<override>(Callback:t -> void)<transacts> : cancelable =
        var Cancelable : my_cancelable = my_cancelable{}
        if (Trigger := GetProxyTrigger()?):
            set Cancelable = my_cancelable{MaybeCancelable := option{Trigger.TriggeredEvent.Subscribe(my_callback(t){Callback := Callback, SpawnAwait := SpawnAwait}.OnSignal)}}
        Cancelable

subscribable_proxytrigger<public> := class(tag){}

my_class<public> := class:
    OnCustomEvent<public>() : listenable(int) = OnCustomEventSignaler
    OnCustomEventSignaler<private> : my_subscribable(int) = my_subscribable(int){}

    SomeInternalMethod<internal>(Number:int) : void =
        OnCustomEventSignaler.Signal(Number)

Havent tried it, but understandable question arises is there easier way :smiley:

2 Likes

Ok this was my solution anyway haha. Thanks for letting me know

3 Likes

I might have found a better way

custom_cancelable<public> := class:
    var Canceled : logic = false

    CancelEvent : event() = event(){}

    Cancel():void=
        set Canceled = true
        CancelEvent.Signal()

custom_subscribable<public>(t:type)<computes> := class<public>(awaitable(t)):

    InnerEvent : event(t) = event(t){}

    Subscribe(Callback:type {__(:t):void}):custom_cancelable=
        Cancelable := custom_cancelable{}
        spawn{_WaitForEvent(Callback, Cancelable)}

        Cancelable

    _WaitForEvent(Callback:type {__(:t):void}, Cancelable: custom_cancelable)<suspends>:void=
        race:
            # Wait for payload to be sent
            loop:
                Payload := InnerEvent.Await()

                # Just in case
                if(not Cancelable.Canceled?):
                    Callback(Payload)
                else:
                    break # Just in case

            # Cancelable got canceled
            Cancelable.CancelEvent.Await()

    
    Signal<public>(Payload:t):void =
        InnerEvent.Signal(Payload)

    Await<override>()<suspends>:t=
        InnerEvent.Await()

These might not be stored along in an array of cancelable, but it seem to work good (forgetting that it uses spawns)

I’m not sure that it works flawlessly for now, so if someone wants to try it out :person_shrugging:

EDIT: For anyone wanting to use this, just need to warn you, the garbage collector will not clean up the suspending calls (which is obvious but I didn’t realise at the time)
So each .Subscribe() call will stay alive untill you manually call .Cancel() or .CancelAll()
So to say, try to not dynamically generate .Subscribe() calls and you should be fine :+1:

7 Likes

I’m getting “Can’t access a function from a preceding type” using this. Do that have any update?

1 Like

I’d like to try it, but like MarcosRocha, I see “Can’t access a function from a preceding type”. Do you have idea that avoid this error?

1 Like

@YuyaShiotani @MarcosRocha

I’m not sure it works fine for me, are you trying to put this in a module or smth ?

looks like the indenting might be a little off? oops, i may be seeing it wrong

Sorry for the delay. I copy pasted your code to a blank file (declaring needed using files) and got that.

1 Like