Jump to content

Big brother is back: Observable everywhere!

Recommended Posts

Hello everybody,

I just wanted to let you know that coming with the 2.4 we have a new Observable Pattern. @Deltakosh is already using them almost everywhere he can for you to observe a particular event.

So how does it works

There're two parts: the Observable and the Observer. The Observable is a property of an object which represent a given event (like BeforeRender for instance). Users that want to have their own piece of code running in response of such event will register an Observer to the adequate Observable. Then it's the duty of the Observable to execute the Observers, when appropriate.

We have two classes

Obsersable<T>the implementer uses it to create a property which will trigger all the registered observers.The Generic type T is used to communicate a given data type from the Observable to the Observer.

You have the following method/properties:

  • add(): to add an Observer
  • remove(): to remove a previously registered Observer
  • removeCallback(): same as above but giving the callback instead of the Observer instance
  • notifyObservers(): used to notify all the registered Observers (with a little special feature I'll detail at the end of this post)
  • hasObserver: a property that returns true if at least one Observer is registered
  • clear() to remove all Observers
  • clone() to simply clone the object but not the registered Observers.

Observer<T> an instance of this class is created when you call the Observable.add() method to create a new Observer. When you no longer want to be notified, you call Observable.remove() giving the same object and you're done!

The Special Feature

Ok, this one is not the easiest thing to catch, but with an example I'm sure everybody will get through it. Let me first explain the intent behind this feature:
Sometime you have many different events that are still part of a "group", then you're faced with the choice of:

  1. Create one Observable per event: it's fine grained/efficient, has good performances, but you have to repeat x time the same implementation in your code with few changes. Perfect example: MouseButtonDown/Up/Click/DoubleClick/Move/Enter/Leave: 7 Observables!
  2. Create one Observable that will aggregate all these events: then in the T data, you specify which "sub-event" it's about (e.g.either MouseButtonDown or MouseButtonUp, etc.) thanks to an enumeration alike data. The implementation is easier because you only have one Observable to code, but the drawback is that people that only care about MouseButtonClick will still be notified for the 6 others sub events! Which is not the best thing performance wise.

The solution: the event mask

When you register an Observer, depending on the implementation of its corresponding Observable, you have the possibility to state which "sub events" you can to be notified about (e.g. MouseButtonClick | MouseMove).
On the Observable side, when the notifyObservers method is called a given mask value will be specifed to state which subevent it's about (e.g. MouseMove), then only the registered Observers with this mask will be notified!

You have the best of both worlds! By default the mask value is always -1, meaning all the bits are set, then all subevent are concerned (which is equivalent to only 1, because there's no distinction).

Give me an example!

var button2Rect = BABYLON.Rectangle2D.Create(canvas, "button2", 200, 500, 100, 40, BABYLON.Canvas2D.GetSolidColorBrushFromHex("#4040C0FF"));
button2Rect.roundRadius = 10;
button2Rect.origin = new Vector2(0.17, 0.33);
button2Rect.levelVisible = false;
text = BABYLON.Text2D.Create(button2Rect, "button2Text", 0, 0, "12pt Arial", "Awesome!", new Color4(1, 1, 1, 1));

// Create an Observer on the pointerEventObservable
buttonRect.pointerEventObservable.add((d, s) => {
}, PrimitivePointerInfo.PointerUp);

The Canvas2D defines for Primitives a "pointerEventObservable" property defined as : Observable<PrimitivePointerInfo>.
The class PrimitivePointerInfo contains all the data sent by the Observable for each Observer callback to know what happened.
But it also defines the different available sub-events:

    export class PrimitivePointerInfo {
        private static _pointerOver        = 0x0001;
        private static _pointerEnter       = 0x0002;
        private static _pointerDown        = 0x0004;
        private static _pointerMouseWheel  = 0x0008;
        private static _pointerMove        = 0x0010;
        private static _pointerUp          = 0x0020;
        private static _pointerOut         = 0x0040;
        private static _pointerLeave       = 0x0080;
        private static _pointerGotCapture  = 0x0100;
        private static _pointerLostCapture = 0x0200;

        public static get PointerOver(): number {
            return PrimitivePointerInfo._pointerOver; }
        public static get PointerEnter(): number {
            return PrimitivePointerInfo._pointerEnter; }
        public static get PointerDown(): number {
            return PrimitivePointerInfo._pointerDown; }
        public static get PointerMouseWheel(): number {
            return PrimitivePointerInfo._pointerMouseWheel; }
        public static get PointerMove(): number {
            return PrimitivePointerInfo._pointerMove; }
        public static get PointerUp(): number {
            return PrimitivePointerInfo._pointerUp; }
        public static get PointerOut(): number {
            return PrimitivePointerInfo._pointerOut; }
        public static get PointerLeave(): number {
            return PrimitivePointerInfo._pointerLeave; }
        public static get PointerGotCapture(): number {
            return PrimitivePointerInfo._pointerGotCapture; }
        public static get PointerLostCapture(): number {
            return PrimitivePointerInfo._pointerLostCapture; }

When we register the Observer, here's what we did:

buttonRect.pointerEventObservable.add((d, s) => {
}, PrimitivePointerInfo.PointerUp);

The first argument is the callback (d, s) are two parameters, d is of type T (then it's PrimitivePointerInfo in our case) and contains the data setup by the Observable, s is of type EventState. EventState is a little class with two properties: skipNextObservers, if you set to true then the subsequent Observers won't be called; mask, which is the mask the Observable used to call the Observers.
Then you have the body of your callback (here defined as a lambda, which only does a console.log)
The last argument is the mask you want. If you don't specify it, you will be notified of everything, in this case we specify PrimitivePointerInfo.PointerUp to only get notified when the Pointer is Up and nothing else.

In the 3D Engine, many handlers were replaced by Observable, there's still a backward compatibility but now, through the Observable, more than one Observer can be notified of a given event.

If you have question, don't hesitate, shoot!

You can take a look at this file for the complete version of the example above.

Link to post
Share on other sites

:)  [Wingy sets-out Java traps.  He thinks this area of the forum... has become infested with Java's.  You know... little nasty, angry possum-like rodents that chew-up datatype flexibility?] 

Man, once they get into the framework of your framework, they'll wreak havoc, building little Java nests in the walls, casting type errors and observabling everything.  Seen it a thousand times!  heh.

Yuh yuh yuh, me thinks we is dragging BJS away from simple land.  I hope I'm wrong.  Is there any way we can wrap these things into something user-friendly, or at least newbie-teachable?  I'm scared... but that's common for me... and my dog.  :)


Link to post
Share on other sites

@Wingnut :lol::D:lol: That was fun >_< 

I can only here talk about my experience, as one of the newbies... BABYLON is a fantastic "common ground" between the end user and the devs. As a newbie, I had to step-up, and the tutorials and docs are perfectly good for that. And also this forum of course ^_^.
Witnessing the roll-out of this new feature, I realize the effort put by the devs to make it "readable" by the mass.

I hope those won't stay internal tools though ... / For my newbie needs, Canvas2D is a Plane that stays on screen ... lol simple, but necessary tool !
Here's an example of how a newbie like me would do :

But I'm sure more demos and PG will come and clarify all this with real-life situations ;-)

Link to post
Share on other sites
16 hours ago, Wingnut said:

:)  [Wingy sets-out Java traps.  He thinks this area of the forum... has become infested with Java's.  You know... little nasty, angry possum-like rodents that chew-up datatype flexibility?] 

Man, once they get into the framework of your framework, they'll wreak havoc, building little Java nests in the walls, casting type errors and observabling everything.  Seen it a thousand times!  heh.

Yuh yuh yuh, me thinks we is dragging BJS away from simple land.  I hope I'm wrong.  Is there any way we can wrap these things into something user-friendly, or at least newbie-teachable?  I'm scared... but that's common for me... and my dog.  :)


I can understand your point, but to be honest, this feature is more for the contributors than the users, but I like to think that it's always a good thing to talk about new features.
However, it doesn't not change a single thing to the user, the easy callback are still here and working for those who need something simple, yet limited.
For those who need more control and abstraction in their code using bjs: Observable is a must have feature.

Not even to mention Canvas2D, I couldn't have wrote this feature without them.

Link to post
Share on other sites

Naz... you're just the cat's meow.  :)  I like clicking flag 2 and flag 6, and watching the skin stretch on Dino's back.  Man, I learn tons from ALL you guys, thanks!

Ok, back to the observables and observers.  I noticed that none of the docs so far, have mentioned onIntersect.  I notice here in Observable source, it says this:


you may have a given Observable that have four different types of notifications: Move (mask = 0x01), Stop (mask = 0x02), Turn Right (mask = 0X04), Turn Left (mask = 0X08).

Now we're visiting the "average user"-level of Observables... used on a mesh.  Let's pretend.  I have a box... using physics, sliding down a ramp.  I have another mesh placed half-way down the ram... a thin line drawn across the ramp (but it IS a mesh).  This line has no physics impostor, so it will not affect the sliding box.  BUT... I want the mesh Observer to have 3 notifications.

#1 - Notify when the box intersects half-way line.
#2 - Notify when the box intersects ground at bottom of ramp.
#3 - Notify when the physics engine has set box impostor to "sleep" (when box finally stops tumbling/sliding across the ground).

Can this be done?  Would someone like to make a demo?  Thanks either way.

Observers need to be "polled" or "message looped", right?  Something down at root level... is checking all registered observers as fast as it can, yes?  I wouldn't mind hearing a few sentences about HOW that happens (but I think I can hack-out an answer, too, with research).  I know you observer pros are pretty busy, but I did hear someone say "If you have question, don't hesitate, shoot!".  :D  I'm a professional question shooter, and world renowned for my question-dumbness levels.  heh

Currently, mesh seems to have three available observables...  mesh.onBeforeRenderObservable, mesh.onAfterRenderObservable, and mesh.onBeforeDrawObservable.  Will this list grow, over time?  Or, can we add our own, as needed?  Any thoughts about this would be very appreciated.

Update:  http://www.babylonjs-playground.com/#UP2O8#3  Look at me GO!  I used a demo from the docs, but put the observer-remover inside an 8 second timer.  Wow!  I'm kickin' butt learning observables, eh?  :D (blatant sarcasm)  I also found onCollideObservable on the abstractMesh class.... so that's all cool.  Wait til we get to the weird ones... like onMaterialLitObservable.  :)  Or maybe we remove "on".  Simply materialLitObservable.  Yeah.

Link to post
Share on other sites

Regarding the inner work: When babylon.js wants to raise an event, it asks the observer to call all observers. Like here:https://github.com/BabylonJS/Babylon.js/blob/master/src/babylon.scene.ts#L1245

The notifyObservers is pretty simple: https://github.com/BabylonJS/Babylon.js/blob/master/src/Tools/babylon.observable.ts#L105

Link to post
Share on other sites

Thanks DK.  But but but... how does the engine "service" those observables?  Is there a scene.observables array that gets iterated-thru... once per frame or faster?  Something has to "poll" the observables, yes?  What creature lives far upstream?  :)  I see no ref to "observ" inside engine.js.  How the heck?  There's always a hook, somewhere in these types of systems.  (From TinyFugue days - hooks and triggers.)   hmm.

Ya know... @Deltakosh  ...  remember that hair-brained scheme I had... in The Wingnut Chonicles... about every mouse or keyboard activity.... FIRST hit an actionManager, and then be properly routed from there?  Yuh, yuh, yuh.  Observers are not much different, in theory.  :)  Just wanted to dish out a little "neener", there.  :)  I was in the right ballpark.  My methods of doing it... were flaky, though.  We would have needed a completely different type of action manager... different purpose.  I think that's what we have... with the "obs" system.  :)

Not sure yet.  Still learning.  Nice job on the backward compat, guys.  It looks like that is/was a serious amount of work.

Link to post
Share on other sites
  • 3 years later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Create New...