If you have a problem or need to report a bug please email : support@dsprobotics.com
There are 3 sections to this support area:
DOWNLOADS: access to product manuals, support files and drivers
HELP & INFORMATION: tutorials and example files for learning or finding pre-made modules for your projects
USER FORUMS: meet with other users and exchange ideas, you can also get help and assistance here
NEW REGISTRATIONS - please contact us if you wish to register on the forum
Users are reminded of the forum rules they sign up to which prohibits any activity that violates any laws including posting material covered by copyright
[Ruby] Intercommunication
12 posts
• Page 1 of 2 • 1, 2
[Ruby] Intercommunication
Sometimes I am excited like a little kid. I just feel like discovering a new world, while I'm probably the billionth to discover this:
One way to establish a communication in other languages are so called "callbacks". Ruby does not supply exactly those types of "callbacks", and so some time ago we had a thread in the user example section, covering different approaches to mimic such a callback.
Now I found an area of Ruby that I never spend much time in. What you can do with it is more than the mechanic of a callback, so I made this new topic.
There's a kernel module called ObjectSpace. It is defined as "containing a number of routines that interact with the garbage collection facility and allow you to traverse all living objects with an iterator."
Now this sounds like I would never need to use it. So I thought. Then I ran into an issue with my ACM.
It is a server/client design of some kind, which means, there's the global mixer module, where you manage all of the colors of your project, and local receiver modules that each provide one of the many colors for use in drawing. The system works wireless, and it needs to work as such, because you may have hundreds of colors throughout your project. As you know, wireless works downwards only. And here's the catch: When you drag a receiver from the toolbox to your schematic, the receiver immediatly needs to know which color groups of what colors currently exist (so that it can display them for you to choose a color)
But the colors are all manged in the mixer module, way up in the schematic and only connected wireless. But inputs of Ruby don't send a back-trigger, they provide you with the values that were last active (in this case when the receiver was copied first into the toolbox).
So, what I need is a way to recognize when a receiver is dragged from the toolbox, then asking the mixer to update its outputs. Houston, we have a problem.
First I found out that dragging from the toolbox is considered duplicating and so an AfterDup prim will trigger as soon as the module containg it "arrives" in the schematic. Phew, issue one solved!
The following may sound like nothing spectacular, but I worked on and off for almost a year on it. I discovered the same problem when really duplicating a receiver via copy/paste. So I concentrated on this first. After desperate trial and error sessions, one day I remembered ObjectSpace and began to read more closely. I found a method called "_id2ref(object_id)". What it does is creating a reference to the object from its id.
Cool, that would be a way to access the RubyEdit that makes up the ACM mixer from the ACM receiver. In the RubyEdit of the mixer I used the init method to output self.object_id to a wireless transmitter. Also I created a method called "refresh" that sends all colorgroups over the wireless link.
In the receiver I created a section in the event method to react on a trigger from the AfterDup prim and doing
I then copied receivers like hell, and it worked! It's alivvvvveee! Yeah! Heureka!
Then I draged the receiver into my toolbox, dragged it back from the toolbox and ... it didn't work. After a few weeks I realized it is because object_id is stored when copied to the toolbox. But it is not static. The next time you use the mixer, object_id will be different. And the mixer's RubyEdit only sends its object_id once, at init.
I needed a way to tell the mixer that things have changed and a receiver needs an update of all the data. Almost giving up, while working on a project for someone, I turned back to ObjectSpace a few hours ago. To my surprise I found the solution. It was there all the time, I just didn't get it. It isn't straight-forward, so maybe this is an excuse.
respond_to?(symbol) is a method that returns true or false. It checks if an object does respond to a call of the method passed as symbol.
ObjectSpace.each_object(module) loops through all active objects (if module is omitted) or all objects of that type of class or module (if module is passed).
From there it was a small step. I replaced above mentioned code with this one:
This goes through all RubyEdit classes currently alive and checks if any of them know the method .refresh, in which case the method is called (which then sends all the information the receiver needs).
So far so good. There is still an issue. Flowstone crashes when I search for the receiver in the toolbox. Maybe it triggers AfterDup also when an object is dragged into the toolbox? That would produces severe issues with ObjectSpace. I will investigate further.
But for now I just enjoy having solved this pesky issue of upward communication between classes.
One way to establish a communication in other languages are so called "callbacks". Ruby does not supply exactly those types of "callbacks", and so some time ago we had a thread in the user example section, covering different approaches to mimic such a callback.
Now I found an area of Ruby that I never spend much time in. What you can do with it is more than the mechanic of a callback, so I made this new topic.
There's a kernel module called ObjectSpace. It is defined as "containing a number of routines that interact with the garbage collection facility and allow you to traverse all living objects with an iterator."
Now this sounds like I would never need to use it. So I thought. Then I ran into an issue with my ACM.
It is a server/client design of some kind, which means, there's the global mixer module, where you manage all of the colors of your project, and local receiver modules that each provide one of the many colors for use in drawing. The system works wireless, and it needs to work as such, because you may have hundreds of colors throughout your project. As you know, wireless works downwards only. And here's the catch: When you drag a receiver from the toolbox to your schematic, the receiver immediatly needs to know which color groups of what colors currently exist (so that it can display them for you to choose a color)
But the colors are all manged in the mixer module, way up in the schematic and only connected wireless. But inputs of Ruby don't send a back-trigger, they provide you with the values that were last active (in this case when the receiver was copied first into the toolbox).
So, what I need is a way to recognize when a receiver is dragged from the toolbox, then asking the mixer to update its outputs. Houston, we have a problem.
First I found out that dragging from the toolbox is considered duplicating and so an AfterDup prim will trigger as soon as the module containg it "arrives" in the schematic. Phew, issue one solved!
The following may sound like nothing spectacular, but I worked on and off for almost a year on it. I discovered the same problem when really duplicating a receiver via copy/paste. So I concentrated on this first. After desperate trial and error sessions, one day I remembered ObjectSpace and began to read more closely. I found a method called "_id2ref(object_id)". What it does is creating a reference to the object from its id.
Cool, that would be a way to access the RubyEdit that makes up the ACM mixer from the ACM receiver. In the RubyEdit of the mixer I used the init method to output self.object_id to a wireless transmitter. Also I created a method called "refresh" that sends all colorgroups over the wireless link.
In the receiver I created a section in the event method to react on a trigger from the AfterDup prim and doing
- Code: Select all
mixer = ObjectSpace._id2ref(@mixer_id) #@mixer_id is the input from the wireless that receives self.object_id
mixer.refresh # calling the method refresh of the mixer's RubyEdit from within the receiver's RubyEdit
I then copied receivers like hell, and it worked! It's alivvvvveee! Yeah! Heureka!
Then I draged the receiver into my toolbox, dragged it back from the toolbox and ... it didn't work. After a few weeks I realized it is because object_id is stored when copied to the toolbox. But it is not static. The next time you use the mixer, object_id will be different. And the mixer's RubyEdit only sends its object_id once, at init.
I needed a way to tell the mixer that things have changed and a receiver needs an update of all the data. Almost giving up, while working on a project for someone, I turned back to ObjectSpace a few hours ago. To my surprise I found the solution. It was there all the time, I just didn't get it. It isn't straight-forward, so maybe this is an excuse.
respond_to?(symbol) is a method that returns true or false. It checks if an object does respond to a call of the method passed as symbol.
ObjectSpace.each_object(module) loops through all active objects (if module is omitted) or all objects of that type of class or module (if module is passed).
From there it was a small step. I replaced above mentioned code with this one:
- Code: Select all
ObjectSpace.each_object(RubyEdit) do |obj|
if obj.respond_to?(:refresh)
obj.refresh
end
end
This goes through all RubyEdit classes currently alive and checks if any of them know the method .refresh, in which case the method is called (which then sends all the information the receiver needs).
So far so good. There is still an issue. Flowstone crashes when I search for the receiver in the toolbox. Maybe it triggers AfterDup also when an object is dragged into the toolbox? That would produces severe issues with ObjectSpace. I will investigate further.
But for now I just enjoy having solved this pesky issue of upward communication between classes.
"There lies the dog buried" (German saying translated literally)
- tulamide
- Posts: 2714
- Joined: Sat Jun 21, 2014 2:48 pm
- Location: Germany
Re: [Ruby] Intercommunication
Why not just use an Observable Pattern?
- Tronic
- Posts: 539
- Joined: Wed Dec 21, 2011 12:59 pm
Re: [Ruby] Intercommunication
Because there is no pattern?
I'm not sure I understand what you mean. Could you please elaborate on that?
I case you mean the "Observable" module, that one is not part of Flowstone's Ruby implementation, unfortunately.
I try to solve this with whatever is on-board, so that I don't need to bundle external files.
I'm not sure I understand what you mean. Could you please elaborate on that?
I case you mean the "Observable" module, that one is not part of Flowstone's Ruby implementation, unfortunately.
I try to solve this with whatever is on-board, so that I don't need to bundle external files.
"There lies the dog buried" (German saying translated literally)
- tulamide
- Posts: 2714
- Joined: Sat Jun 21, 2014 2:48 pm
- Location: Germany
Re: [Ruby] Intercommunication
this is what I use:
in some ruby module add the add observer
and in other ruby module use the notify observer
- Code: Select all
module Observable
def add_observer(name,id=nil)
observer = self
observers[observer][name] = id
end
def delete_observer(name)
observer = self
observers[observer].delete(name)
end
def notify_observer(name, id, *args)
observers.each do |observer,names|
if observer.respond_to? name
if names.include? name
if names.fetch(name) == id
observer.public_send(name, observer => args)
end
end
end
end
end
def notify_observers(name, *args)
observers.each do |observer,names|
if observer.respond_to? name
if names.include? name
if names.fetch(name).nil?
observer.public_send(name, observer => args)
end
end
end
end
end
def observers
@@observers ||= Hash.new{|h,k| h[k] = {}}
end
end
class RubyEdit
include Observable
end
in some ruby module add the add observer
- Code: Select all
add_observer(:fun,:hot)
add_observer(:fuz)
def fuz(*args)
watch 'fuz', args
end
def fun(*args)
watch 'fun', args
end
and in other ruby module use the notify observer
- Code: Select all
notify_observers(:fuz,"zap")
notify_observer(:fun,:hot,"huz")
- Tronic
- Posts: 539
- Joined: Wed Dec 21, 2011 12:59 pm
Re: [Ruby] Intercommunication
So you created your very own Observable module!
That's pretty cool. Thank you very much for sharing. Will report back in a few days if it worked (well, it will work, but if it works better).
That's pretty cool. Thank you very much for sharing. Will report back in a few days if it worked (well, it will work, but if it works better).
"There lies the dog buried" (German saying translated literally)
- tulamide
- Posts: 2714
- Joined: Sat Jun 21, 2014 2:48 pm
- Location: Germany
Re: [Ruby] Intercommunication
If you are interested in the subject,
I can also show an Event Emitter implementation.
I can also show an Event Emitter implementation.
- Tronic
- Posts: 539
- Joined: Wed Dec 21, 2011 12:59 pm
Re: [Ruby] Intercommunication
Tronic wrote:If you are interested in the subject,
I can also show an Event Emitter implementation.
Similar to JS? Like 'addListener', 'on event', etc.?
Yes, please, that would be very helpful!
"There lies the dog buried" (German saying translated literally)
- tulamide
- Posts: 2714
- Joined: Sat Jun 21, 2014 2:48 pm
- Location: Germany
Re: [Ruby] Intercommunication
Here as you can see there is the base of an event system in ruby
and the proper operation of how to perform callbacks in ruby.
This module gives you the ability to have a global event system,
but you can use the same code, only by changing the variable container of callbacks,
instead of using a global variable, use a local variable,
and then add the module to your classes, so it can work locally with your class.
the global event emitter module
add some listener
generate some emitter
callback me?
and the proper operation of how to perform callbacks in ruby.
This module gives you the ability to have a global event system,
but you can use the same code, only by changing the variable container of callbacks,
instead of using a global variable, use a local variable,
and then add the module to your classes, so it can work locally with your class.
the global event emitter module
- Code: Select all
module GlobalEventEmitter
def _callbacks
@@_callbacks ||= Hash.new { |h, k| h[k] = {} }
end
def on(type, &block)
listener = self
_callbacks[type][listener] = block if block_given?
end
def emit(type, *args)
_callbacks[type].each do |listener,block|
return block.call(*args)
end
end
end
class RubyEdit
include GlobalEventEmitter
end
add some listener
- Code: Select all
on(:this_function) do |args|
# the last parameter is always returned
# to the emitter as callback
# in this case it return args modified
args[:knob_1] *= 0.5
args[:knob_2] += 0.1
args
end
on(:drawLine) do |a|
# this draw an line as callback
p = Pen.new (Color.new(255)), 1
a.drawLine p, [0,0],[2,2]
end
on(:message) do |a|
# this output to the emitter module
a.output 0, "hello"
# here return true as callback value
true
end
generate some emitter
- Code: Select all
def draw v
emit(:drawLine,v)
end
watch 'cbk_1', @cbk_1 = emit(:message,self)
watch 'cbk_2', @cbk_2 = emit(:this_function,:knob_1 => 0.1,:knob_2 => 0.8)
callback me?
- Tronic
- Posts: 539
- Joined: Wed Dec 21, 2011 12:59 pm
Re: [Ruby] Intercommunication
on(:tronic) do |thank_you|
thank_you.because = "this is so elegant yet lightweight!"
thank_you.also = "because it is so easy to understand."
thank_you.finally = "for an implementation that makes communication a lot easier!"
end
awesome = emit(:tronic, laudation)
thank_you.because = "this is so elegant yet lightweight!"
thank_you.also = "because it is so easy to understand."
thank_you.finally = "for an implementation that makes communication a lot easier!"
end
awesome = emit(:tronic, laudation)
"There lies the dog buried" (German saying translated literally)
- tulamide
- Posts: 2714
- Joined: Sat Jun 21, 2014 2:48 pm
- Location: Germany
Re: [Ruby] Intercommunication
I want to also share this thing (a little magic?):
if you create a primitive module and give it a name in the caption:
___SYSTEM___
it will work to the ruby interpreter level (main),
if you try to write the self function method in a ruby module,
you'll see that it will not give you a RubyEdit instance,
but it will result in the 'main instance of the ruby interpreter',
then, in this primitive module add all your RubyEdit modules, classes, and codes that you want them declared before all things,
this solves the stress of adding new things to the whole Ruby Flowstone System
without having to take account of the order of creation of RubyEdit modules,
so in short, everything that contains this primitive module will always be loaded before anything else.
try it, and then tell me.
Edit: some correction in the text
if you create a primitive module and give it a name in the caption:
___SYSTEM___
it will work to the ruby interpreter level (main),
if you try to write the self function method in a ruby module,
you'll see that it will not give you a RubyEdit instance,
but it will result in the 'main instance of the ruby interpreter',
then, in this primitive module add all your RubyEdit modules, classes, and codes that you want them declared before all things,
this solves the stress of adding new things to the whole Ruby Flowstone System
without having to take account of the order of creation of RubyEdit modules,
so in short, everything that contains this primitive module will always be loaded before anything else.
try it, and then tell me.
Edit: some correction in the text
Last edited by Tronic on Tue May 24, 2016 10:46 pm, edited 1 time in total.
- Tronic
- Posts: 539
- Joined: Wed Dec 21, 2011 12:59 pm
12 posts
• Page 1 of 2 • 1, 2
Who is online
Users browsing this forum: No registered users and 5 guests