Jump to content
Dalija Prasnikar

ANN: Open Source Event Bus NX Horizon

Recommended Posts

 I have published initial release of NX Horizon Event Bus for Delphi as Open Source on GitHub.

 

Features:

  • implements the publish/subscribe pattern, decoupling publishers and subscribers
  • events are categorized by type
  • any Delphi type can be used as an event
  • supports four types of event delivery: synchronous, asynchronous, synchronous in the context of the main thread, and asynchronous in the context of the main thread
  • provides generic record and reference-counted class wrappers for easier usage and management of existing types as events
  • simple in both implementation and usage
  • fast
  • full thread safety


https://github.com/dalijap/nx-horizon

  • Like 9
  • Thanks 7

Share this post


Link to post

Thanks for the nice and clean library, looks very good.

What I'm missing still, same as in the DEB library, is the possibiltiy to easily subscribe and use anonymous methods instead of event methods.

Why is that, and is there any plan to add such feature later on ?

 

Share this post


Link to post
32 minutes ago, Rollo62 said:

Thanks for the nice and clean library, looks very good.

Thanks!

Quote

What I'm missing still, same as in the DEB library, is the possibiltiy to easily subscribe and use anonymous methods instead of event methods.

Why is that, and is there any plan to add such feature later on ?

There are few reasons why they are not implemented as of now. 

 

First, I wrote this for my own needs and in my code I used regular methods, so I didn't had immediate need for anonymous methods. Next, I wrote about this event bus in my recent book Delphi Thread Safety Pattern, so I wanted to keep code as minimal as possible and focused on bus itself.

 

Anonymous methods are definitely one of the potential future enhancements, but I wanted to publish the code as soon as possible instead of waiting to polish it more as this might have postponed release indefinitely.

Edited by Dalija Prasnikar
  • Like 1

Share this post


Link to post

Thanks for the insights.

I just started to read your new book for some days now, since I get distracted here and there, but I already can say its very well written book and nicely structured.

I hope to see more books like that, to core topics, from you soon.

  • Like 2

Share this post


Link to post

The project seems to be very interesting.

 

So far I have no experience with an event bus to be able to estimate whether I can use the library in my small hobby projects.

 

For me (and I'm sure for others) a few small practical examples would be very instructive. It would be great if you could upload some examples to the github repository.

Share this post


Link to post
13 hours ago, ringli said:

The project seems to be very interesting.

Thanks!

13 hours ago, ringli said:

So far I have no experience with an event bus to be able to estimate whether I can use the library in my small hobby projects.

 

For me (and I'm sure for others) a few small practical examples would be very instructive. It would be great if you could upload some examples to the github repository.

I will try to add some. I will need some time to prepare some meaningful examples that can show potential use cases.

 

Event bus is a messaging system. Delphi already has basic event bus implementation in System.Messaging https://docwiki.embarcadero.com/CodeExamples/Alexandria/en/System.Messaging_(Delphi) You can also look at the examples there as those use cases apply to my event bus, too.

 

Main difference is that System.Messaging is not thread-safe and you can only use it to send messages in the context of the main thread. If you want to send messages across multiple threads you need a thread-safe event bus, like NX Horizon. Because, it is thread-safe, it also has some additional features like dispatching events (messages) asynchronously in the background thread.

 

Maybe the easies way to explain what is event bus is comparing it to a Button OnClick event handler. When user clicks a button code in the OnClick event handler will run. main difference (besides multithreading support) is that with button and its event handler there is usually deeper connection and there is direct link with the button and its event handler. For instance if you click Help button on some form, you would want to open Help window from its OnClick event handler. But in that case your form with button needs to know about help form. If you have many forms that need to open help form will create tight coupling between all those forms and help form. 

 

With event bus, you can declare TOpenHelp event type and then you can subscribe some code to such event type. In your forms with help buttons, you would still need OnClick event handler, but instead of directly opening help form from that OnClick event you can send a message to event bus that TOpenHelp event happened. And then subscribers to that event (there can be more than one) will receive it and run the appropriate code in associated subscription event handler. This way your forms don't need to know about your help form, and code handling your help form does not need to know from where TOpenHelp came from. 

 

Event type also serves two purposes. Its type tells that particular event happened, and its content (event can be any automatically managed or value type) is used to pass additional data. for instance if the TOpenHelp is integer type, you use it to store and pass help page number depending on which help button is clicked and then you can open help on particular help page. 

 

Another example would be downloading some files in the background thread and then sending TDownloadCompleted event from that thread with some data about particular download and then subscribers can handle and do whatever they need to do with that data. Process it further, show it to the user, or anything else. 

  • Like 2

Share this post


Link to post

Thanks for your explanation and your efforts.

 

In one of my projects I am reading in a folder with more subfolders and outputting the search results in a ListView.

 

The whole thing is already running in a simple thread, but sometimes it seems to hang a bit. Therefore I wanted to test if there is a better or simpler solution. I always tend to solve everything too complicated. :classic_blink:

Share this post


Link to post
14 hours ago, ringli said:

The whole thing is already running in a simple thread, but sometimes it seems to hang a bit. Therefore I wanted to test if there is a better or simpler solution. I always tend to solve everything too complicated. :classic_blink:

If you are already running it in a thread, then adding event bus on its own will not solve your problem. 

 

Even bus would help decoupling your code that is doing the search from the code that is showing the results, but it will not run faster. On the contrary. Since you are adding results to the UI, that part needs to run in the main thread. So you would use TThread.Queue or TThread.Synchronize. Event bus would call the same code in order to run event handler in the main thread. But when you do that you are calling it directly. Sending message through event bus runs a bit more code. You need to create message (event) which will cost some time, depending on the event type and the data it passes. Then sending message alone will lock the collection of subscribers, iterate through that collection, locate appropriate subscriber and then it will invoke event handler for that subscriber. And when iteration is completed it needs to unlock collection. 

 

So decoupling comes at some price. In most cases that price is well worth paying, because bus overhead is very small comparing to other code that runs, but it will definitely not run faster than some code that is directly wired and invoked.

 

Why is your code having problems, is hard to say without seeing the code. Also, when you are searching on disk, performance will be tied with the content of the disk, and its hardware characteristics, as well as the whole system. For instance, if you try to access physically damaged part of the disk OS call may hang on such spot for minutes. 

Share this post


Link to post

Very nice project and thanks for sharing!
For classification: I am just a hobby programmer.

 

In one project I'm currently trying to remove my circular references to other units (decoupling). 
Simple example: I remove my units under uses and want to change a Label.Caption from another form (Unit B) via the form of Unit A using an OnClick event....
Unfortunately I don't know how to implement this with your project.

I am missing a simple example to understand how to implement this.
So I would also be very happy to see a simple example.

 

Thanks.
Note: translated with DeepL

Share this post


Link to post

@juergen

 

as this topic is about "specific" subject from Dalija, I will send a private message with a simple project with 3 forms showing like do it:

  • each "Observer" should subscribe itself a "Subscriber" list
  • each "Subscriber" should have methods to "notify" who is "observing" the changes
  • to centralizate all messages, you can use a "container" or any other way to "receive" and "send" the messages for all or some!
  • an "Observer" can be a "Subscriber"
  • a "Subscriber" can be a "Observer"
  • but you need controls how all works for avoid conflicts of interest, for that, you can use the "container" like mid-term between it!
  • for better job, you use "2 interfaces (IObservers and Observables - or any other name that desire )" for concentrate all methods necessary to dialog between class envolved!
Edited by programmerdelphi2k

Share this post


Link to post
9 hours ago, juergen said:

So I would also be very happy to see a simple example.

Here is simple example:

 

Declare event in some independent unit C

type
  TMyEvent = type string;

Unit A

procedure TFormA.ButtonClick(Sender: TObject);
begin
  NxHorizon.Instance.Post<TMyEvent>('New Caption');
end;

 
Unit B

type
  TFormB = class(...
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  protected 
    fMyEventSubcription: INxEventSubscription;
    procedure OnMyEvent(const aEvent: TMyEvent);    
  end;
    
procedure TFormB.FormCreate(Sender: TObject);
begin
  fMyEventSubcription: := NxHorizon.Instance.Subscribe<TMyEvent>(MainAsync, OnMyEvent);
end;

procedure TFormB.FormDestroy(Sender: TObject);
begin
  if Assigned(fMyEventSubscription) then NxHorizon.Instance.Unsubscribe(fMyEventSubscription);
end;

procedure TFormB.OnMyEvent(const aEvent: TMyEvent);
begin
  Label.Caption := aEvent;
end;

 

Since you are working with GUI, events should be dispatched either as MainAsync or MainSync and that will ensure they run in the context of the main thread. Also, because they run on the main thread, you can just unsubscribe and you don't need to WaitFor subscription to finish work as there will be no active work done in the background threads.

 

I have put subscribing and unsubscribing in FormCreate and FormDestroy event handlers, but you can also put them anywhere else where you are doing form initialization or finalization. Or, you can subscribe and unsubscribe at some other point.

 

  • Like 1

Share this post


Link to post

Nice! I'm currently using events from Spring4D and at first sight, this looks similar. Or am I missing something ?

Share this post


Link to post
25 minutes ago, John R. said:

Nice! I'm currently using events from Spring4D and at first sight, this looks similar. Or am I missing something ?

I have no idea how similar it is to Spring Events as I never used them. 

  • Thanks 1

Share this post


Link to post

@programmerdelphi2k,
Oh, thank you very much for your effort! I will have a close look at it all tonight and then I will have learned something again 🙂

 

@Dalija Prasnikar, Wow, I am happy. Thank you for this very good sample and the explanations

Edited by juergen

Share this post


Link to post

Spring4D Events are just multicast events (like your regular OnClick but with possibly multiple handlers) - an event bus is more.

  • Like 1
  • Thanks 2

Share this post


Link to post
17 hours ago, Wagner Landgraf said:

When developing, did you check https://github.com/spinettaro/delphi-event-bus and if yes, why it didn't serve your purpose?

I was already working on my event bus when I discovered DEB. I would still work on mine even if I knew about DEB sooner, because I also wrote about it in my book and I cannot reason about other people's thought process :classic_smile: 

It is also published as part of book code examples, but it is a feature that deserves separate repository not tied to the book.

 

When it comes to why DEB would not fit my purpose (if I ignore the book part), is that it has more complicated code and more features that don't necessarily bring anything of value to me (for instance, using attributes for setting subscriptions), but on the other hand have big impact on performance. My even bus also supports all types as messages, so there is additional performance boost and avoiding unnecessary memory allocations when message fits into integer or small record type. And at last, my even bus has waitable subscription, so you can wait that all dispatched messages are being fully processed. This is critical feature for more complex cleanup scenarios - yes, it can also be achieved through other means, but it is easier when it is built in feature, and I use it a lot.

 

And the most personal reason is that my code is more compatible (similar setup and calls) with my old thread-unsafe observer framework, and I can more easily and gradually refactor old code to using new one.

  • Like 5
  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×