Support

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 namespace best practice.

For general discussion related FlowStone

Ruby namespace best practice.

Postby trogluddite » Fri Jan 04, 2013 7:33 pm

So there's been quite a bit of debate about the Ruby implementation- particularly around the issue of ensuring that libraries are available, and avoiding clashes between different plugins and modules.
Where libraries are kept, and the bundling of Ruby with exports we can't do much about, apart from to talk to the developers about our worries. But for purely Ruby-coded new classes. modules and methods, there are many things that we can do as programmers to minimise the chance of problems.

The trouble is that the Ruby namespace is global to everything sharing the same Ruby interpreter - whether its several schematic open at once in the editor, multiple plugin instances, or naturally all of the modules within the same schematic. Whenever a class, method or module gets defined in more than one place, the two definitions will clash - possibly re-defining methods, changing variable values etc.
So downloading a nice shiny new module from the forum could create headaches if the developer made some new classes that happened to share names with those in another module elsewhere in your design.
I have seen some good examples of programmers dealing with exactly this issue elsewhere, so I though I'd pass on a quick summary of the 'best practices' that I've come across...

1) Take good care of the name-space
By far the most common time that a class name gets used directly is when creating a new object. Once created, you'll normally have a variable that you use to reference the object. So it's not a big deal if class names are a bit verbose. Classes and modules can also be nested, to isolate them from the rest of the namespace...
Code: Select all
module Trog
|   class Thingy
|       def initialize(name)
|            @name = name
|       end
|   end #class
end # module

Now you cannot access any class methods of class "Thingy" unless you prefix them with the module name "Trog".
Code: Select all
@a_thingy = Trog::Thingy.new("Testing")

The small amount of extra typing in the code is well worth it - now my class "Thingy" won't clash with Fred's class "Fred::Thingy". If we all tag our classes like this, then there will be less clashes - and using your name or "Tag" for the module name means that it simple to find the right people to try and resolve any outstanding problems.
In effect, each developer uses their own personal name space.

2) Global variables and constants
Again careful naming is the key here - but ask yourself if you really need things to be quite so global. Like classes, you can wrap constants and class variables into a Module namespace...
Code: Select all
module Trog
|    @@class_var = "Hello"
|    CLASS_CONST = "I'm Trog"
|           def Trog.class_var  # Accessor method for @@class_var
|                @@class_var
|           end
end # module

The class variable and contant are still effectively 'global', since the module itself is global - we just need to invoke the namespace or the variable accessor...
Code: Select all
a = Trog::CLASS_CONST
a = Trog.class_var


3) Standard Ruby and API classes
Never do anything that would change the behaviour of any of these when used in any of the myriad of ways that they can already be used.
Very occasionally it can really save your neck if you add a new method to an existing class. But even this should be a last resort - usually whatever the problem is can be got around by using module/class methods, or by making a new class that is sub-classed to the one you're trying to target.
As we've seen with FS3.01, the developers are adding new classes and methods from time to time - so even a brand new method that you added could have its feet trodden on in the future!

4) Ruby running order
Rubies get parsed at startup in the order of the module nesting within your schematic, and by the order that you put them into the schematic.
This can create problems where you are trying to call a new class or module from within another RubyEdit instance - because you might make the method call before the class definition has been parsed.
The safest way around this is to only call new classes from within the "event" section of your Ruby code - or methods that you know will only be called once "event" is running. If you call a new class within the "init" or "bare code" sections of a RubyEdit, all bets are off, and it will depend entirely on the parser whether those calls will resolve or not.
If you do need to initialise anything that requires a new class, you can easily do it by calling the intialisation from "event" depending on the setting of a flag....
Code: Select all
def make_new
|    #blah blah...
|    @start_done = true
end

def event i,v,t
|    make_new unless @start_done
|    #blah blah...
end

You could also cut and paste the class definition into every module that needs it, just to be on the safe side. That would mean a lot of extra code to parse at load time though, rewriting the class definition over and over but without actually changing it.
You can get around this by checking whether the class already exists, and only parsing it if it hasn't been run yet...
Code: Select all
unless Module.const_defined?(:NewClass)
|    class NewClass
|        def.......
|    end # class
end # test if defined

...and there are similar ways to check if a class already has a particular method etc.

So, there are many things that we can do to make our code as robust as possible. When within the FS editing space, we just have to be careful about any new modules that we write or download. In time, the best way will become clear, we can all share it, and the "gurus" can point out potential problems in folks code if they ask.

PS) Don't try to copy and paste any of the code examples - I added the pipe characters '|' because the 'code' BBCode strips whitespace, and I can't stand to see code without nice neat indenting!!

And many thanks to: TIG, ThomThom, Fredo6, Dan and many others who develop Ruby plugins over at the Sketchup forums - their Developer section is well worth browse as an example of how a forum community can get together to minimise these sorts of hassles.
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby namespace best practice.

Postby infuzion » Sat Jan 05, 2013 7:33 am

Great analysis Trog! It is great you did so much research!

But dang, this is beginning to look like real work, not the connect-the-dots as SM started... why not just learn C++ at this point?
infuzion
 
Posts: 109
Joined: Tue Jul 13, 2010 11:55 am
Location: Kansas City, USA, Earth, Sol

Re: Ruby namespace best practice.

Postby tester » Sat Jan 05, 2013 1:28 pm

Good point. I think, FS will divide in the future further into low-level programming and visual-object oriented one, i.e. folks who rather create functional modules (based on ruby?) and folks who work with visual objects. Maybe that's the idea - subforum "functional (ruby) modules"? ;-)

On the other hand - FS is a still not so bad "gui maker" with some visual-based advanced functions, that cover a lot of applications. I don't know - is there in C++ such an "extension for dummies"?
Need to take a break? I have something right for you.
Feel free to donate. Thank you for your contribution.
tester
 
Posts: 1786
Joined: Wed Jan 18, 2012 10:52 pm
Location: Poland, internet

Re: Ruby namespace best practice.

Postby trogluddite » Sat Jan 05, 2013 6:06 pm

tester wrote:Good point. I think, FS will divide in the future further into low-level programming and visual-object oriented one, i.e. folks who rather create functional modules (based on ruby?) and folks who work with visual objects.

I think that may be right. But in a way that is just an extension of what already happened with audio DSP in SM...
Only a relatively small number of SM'ers really got into the assembly side of things - but those that did were able to extend the functionality of SM by creating optimised modules that everyone else could just stick in their toolbox and use in the 'visual' way. A well written module is one where the programming inside is almost irrelevant and 'invisible' - all you need to know is how the inputs affect the outputs.

I think Ruby will be the same. I would guess that treating Ruby as "green code" for awkward maths and logic functions is how most people will use it - writing new modules and classes will be something that very few people will probably want to bother with.
But if a new Ruby class could be imported into a schematic as easily as putting a module in the toolbox, then you effectively can just add new commands to the Ruby language within FS. No matter how much code goes into making that new class, everyone can benefit from some new commands that let them perform a complex task with only a handful of code lines.
To take an example from the graphics stuff I posted recently - I wonder how often people get into a bit of a mess when calculating areas for GUI views. I'm sure we've all looked inside a schematic before and seen that; Area to Float - loads of maths - Float to Area combination. Even simple ones get quite messy, and just from looking at it, it isn't intuitive what it does to the area 'geometrically'. The visual way of working is valuable, and I love to use it, but here we've found a task that isn't very well suited to that form of visualisation.
So we could fire up a Ruby primitive, and use something more akin to mathematical equations to reshape the area...
Code: Select all
# Get view area
x, y, width, height = 0, 0, *getViewSize
# Find Centre
cx, cy = x + width * 0.5, y + height * 0.5
# Scale area about centre
width *= scale
height *= scale
# Make area square
width = height = [width, height].min
# Get the output area
x = cx - (width * 0.5)
y = cy = (height * 0.5)
area = [x,y,width,height]

Neater, and probably a little easier to see what is going on geometrically (helped by the copious comments in the code!).
Or you could load in the custom "Trog::ViewArea" class, open a ruby box, and write...
Code: Select all
viewArea = Trog::ViewArea.new(getViewSize)
smallArea = viewArea.scale(0.5).square

The code now barely needs commenting because the method names tell you exactly what's going on. No need to decide in advance what you want to do, and then work out the equations, and then turn the equations into primitives and links, or code - you can just describe to Ruby in geometric terms what you want to happen. Making alterations, or just experimenting for fun with different transformations is now available with a few key-strokes.

This is the kind of thing that well written Ruby modules and classes could bring to FS. Which is why I'm so keen to see it set out on the right foot. Done badly, it could be a nightmare of clashes and bugs - and would soon lose user's trust that the new modules were worth having. But done well, it can really make complex things simpler in a way that would benefit everyone.

So all this talk of "hard work" seems strange to me. Writing new modules, classes etc. is about taking away the hard work by only doing the tricky bits once. Part of the reason I'd like to be able to do this is because I can see how it will speed up my own projects by building a toolkit of Ruby objects that save me having to re-write the same code over and over again.

infuzion wrote:But dang, this is beginning to look like real work, not the connect-the-dots as SM started... why not just learn C++ at this point?


But I still don't feel like straying too far from the visual side of things. C++ has tempted me a few times, but it just doesn't have the fluidity of working in FS/SM. I think that boils down to two things...

1) Everything is real time. No messing around with compiling changes, you get the results of any changes you make instantly. This really suits my haphazard way of working. I've never just sat down and planned in advance what a project is going to be and how it will work - I just follow my nose. The real time feedback just makes the experimentation involved so much more enjoyable.

2) You can quickly pick and choose the fastest/easiest//clearest/etc. way to program anything. Quick DSP experiment - use DSP Code. Bit of a critical module that needs to be fast - use Assembly. Data handling - Ruby.. Overall system architecture - visual blocks. I find that really helps me to focus the time I have available on the most critical areas - I can get stuck into coding wherever performance is critical, but I also have the option to be incredibly lazy and drag in some 'ready-mades' if that suits me better.
Last edited by trogluddite on Thu Jan 10, 2013 1:54 am, edited 1 time in total.
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby namespace best practice.

Postby TomC » Sun Jan 06, 2013 1:01 am

trogluddite wrote:The trouble is that the Ruby namespace is global to everything sharing the same Ruby interpreter


Since I've never written a single line of Ruby code I don't know if this is possible: in Java, packages can have the reverse domain name (if you have an own (unique) one of course) as part of the complete name.

If this is possible (maybe not with the exact syntax, but the same principle), it would be even better to use that name-scheme.

Assume you have the domain trogluddite.com, and you write a project KillerSynth.

The module from above would look like

Code: Select all
    module com.trogluddite.KillerSynth
    |   class Thingy
    |       def initialize(name)
    |            @name = name
    |       end
    |   end #class
    end # module


Tom
TomC
 
Posts: 13
Joined: Fri Jul 16, 2010 10:38 pm

Re: Ruby namespace best practice.

Postby trogluddite » Wed Jan 09, 2013 9:55 pm

Hi Tom,

I'm not sure if Ruby can do it exactly like that, but you certainly can nest namespaces to an arbitrary depth. So something like, Trog::Effects::Delay::Buffer. In Ruby that come out like...
Code: Select all
module Trog
|   module Effects
|       module Delay
|           module Buffer
\                #MAKE STUFF
|           end
|       end
|   end
end

It can get a bit cumbersome when the path is long, but it's simple enough to assign a local variable to the required target...
Code: Select all
@fx = Trog::Effects::Delay::Buffer
@buffer = @fx.new(44100)
Last edited by trogluddite on Thu Jan 10, 2013 2:01 am, edited 1 time in total.
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby namespace best practice.

Postby ccpurno » Wed Jan 09, 2013 10:51 pm

Thnx for this essential info. User guide material if you ask me ;-)

Cc
ccpurno
 
Posts: 14
Joined: Tue Nov 28, 2006 7:15 pm
Location: NL

Re: Ruby namespace best practice.

Postby trogluddite » Thu Jan 10, 2013 1:04 am

Thanks,
I was thinking of asking Malc if maybe we could have a specific "Ruby developer" forum for this kind of stuff. I'm sure that there'll only be a small number of people who want to dig into Ruby with far - but you're right, this kind of stuff easily gets buried when it's just a random thread posted under "General".

I've just been wondering if this namespace stuff could be used by the dev's as a way of isolating plugin instances.

All of our own Ruby code takes the form of RubyEdit instances - but that is largely invisible to end users, and we wouldn't refer to that class within our own code.
You can also easily make exact copies of a class within a new module.
So maybe, it could be done so that each schematic receives a unique ID, which is then used as the name of a new module. Define the RubyEdit class within this module, and it will be separate from the RubyEdit classes of other modules.
Then add an invisble "header and footer" to the code in each Ruby editor "module XX... ...end", wrapping the user's code in the same module as the RubyEdit instances.
From a user perspective this would seem no different than it is now - we are always working within a class instance anyway - who cares if it is called RubyEdit or FSM0184552946::RubyEdit ?

This wouldn't help with any of the other 'dll' issues of course, but if changing that turns out to be impossible due to the structure of Ruby, at least this might let us run simultaneous plugins without so much danger of there being clashes between them.
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby namespace best practice.

Postby Tronic » Tue Apr 30, 2013 1:59 pm

this work, but
Code: Select all
module Tronic
   module MyModule
      def this
         'hi'
      end
      
   end
   include MyModule # include nested module in parent
end

watch 'module ancestors', Tronic.ancestors
# => [Tronic, Tronic::MyModule]

extend Tronic
this
# => hi

The first example works, but in the following it does not include the class.

Code: Select all
module Tronic
   module MyModule
      class MyClass
         def this
         'hi'
         end
      end
   end
   include MyModule
end

watch 'module ancestors', Tronic.ancestors
# => [Tronic, Tronic::MyModule]

extend Tronic
MyClass.this
# => unitialized constant MyClass

I once gave the function
extend Tronic
I wish that it contained all the nested modules and classes.
can do what I think?

thx for any help on this.
Tronic
 
Posts: 539
Joined: Wed Dec 21, 2011 12:59 pm

Re: Ruby namespace best practice.

Postby trogluddite » Tue Apr 30, 2013 9:39 pm

Yes, that's the defined behaviour for modules - 'extend' only adds the instance methods of the Module to the extended object. The class will be there only under its full namespace Tronic::MyModule::MyClass. The 'namespacing' and the 'method container' behaviours are best thought of almost as two almost completely separate features.

There are also a couple of other interesting points that those codes bring up...

The final 'MyClass.this' call in example 2 would not work even if the class were visible at that point in the code - you would get a "No Method" error.
That's because the 'this' method is an 'instance method' for objects created using the class, not a method of the class itself. This is where Modules and Classes differ greatly.

A module is effectively just a dumb container - it doesn't do very much all by itself, it's just somewhere handy to keep methods that you might want to use later, or for creating a namespace to isolate things.

A class is a way of defining a whole new kind of object in Ruby and defining how those objects should behave.- the 'instance methods' made in there apply to individual examples of new objects that you ask the class to build for you.
For example - here's a very simple class defining a new type of object called "Door", and a small Ruby primitive that uses it...
Simple Class Door.fsm
(978 Bytes) Downloaded 1061 times

The syntax "MyClass.this" is different - it is telling the class to do something, not a particular instance of the class. The most common example of that is '.new', for telling the class to give you a new instance - once you have an instance to work with, the class name is hardly ever mentioned again in most code because you'll be referring to the object using a variable.

But you can define your own 'class methods' if there is something you need to do that does apply to the class as a whole - setting a default value that you want to use for new instances, for example...
Code: Select all
class Door
   
  def self.set_default(state)   # The 'self.' tells this to be a class method
    @@default_open = state  # '@@' means a class variable, usable by ALL instances.
  end

  def initialize  # For setting up a new Door object.
     @open = @@default_open
  end

  etc....

end

Door.set_default(true)
my_door = Door.new  # Now created as an open door!


Modules can use this same 'self.' syntax too, to make 'module methods' - for example, those 'Math' trig functions that I mentioned in the earlier post. The 'Math.sin()' version is slightly different to the one that gets added by 'extend', and could have been defined using something like...

Code: Select all
module Math

  def self.sin(angle)  # a 'module method' called using 'Math.sin()'
     # MATHS, ARRGH
  end

  def sin(angle)  # the instance version used for 'extending' objects
    # MATHS.ARRGH
  end

end
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Next

Return to General

Who is online

Users browsing this forum: Google [Bot] and 89 guests