Android Question Select a class at runtime

max123

Active Member
Licensed User
Hi all,

in my project I've 2 classes I wrote. These classes are substantilally viewers tha show a scrollable Canvas inside a big ScrollView2D panel (many thanks to @Informatix for his good ScrollView2D library). This way I've a big 2D scrollable canvas. It is used to simulate a 3D Printer or CNC machine.

Both classes have same methods with same arguments, the only difference of both classes are that the first one show a 2D view, the second one show a static 3D view I've created on a canvas using math calculations.

In my project currently I can switch from 2D view and 3D view just by declaring the right class I want as global variable (in Globals sub) this way:
B4X:
' Uncomment just one
'Private Viewer As GCodeViewer2D
Private Viewer As GCodeViewer3D

This works, but now I want to add in the app settings the ability to switch from 2D to 3D view, so can be selected from end user, then when app restarts, it read from settings what view to use and use it.

What I need is the ability to declare a right class at runtime, but it need to be globally scoped because the app use it in many functions. Something like this:
B4X:
    If Settings.UseViewer2D Then
        Dim Viewer As GCodeViewer2D  ' Must be a global variable
    Else If Settings.UseViewer3D Then
        Dim Viewer As GCodeViewer3D  ' Must be a global variable
    End If
 
    ......
 
    ' Use the right viewer class 2D or 3D
    ' NOTE: both classes have same methods and any sub the same arguments
    Viewer.Initialize(pnlViewer, 1.0, Settings.Antialiasing)
    Viewer.Show(5dip, 5dip, 60%x, 70.7%y)
    Viewer.SetDrawingPlaneSize(Settings.X_MAX_POS, Settings.Y_MAX_POS)  ' Setup drawing plane for a machine.
    '    Viewer.Resize(5dip, 5dip, 60%x, 70.7%y) ' Resize the viewer pamel
     
    If Not (Settings.RoundPlane) Then
        Viewer.setStartPoint(0, 0, 0) ' Set start point to X0 Y0 Z0
        Log("Viewer StartPoint: 0,0,0")
    Else
        Viewer.setStartPoint(Settings.X_MAX_POS*0.5, Settings.Y_MAX_POS*0.5, 0) ' Set start point to X0 Y0 Z0
        Log("Viewer StartPoint: " & (Settings.X_MAX_POS*0.5) & "," & (Settings.Y_MAX_POS*0.5) & ", 0")
    End If

    ......

I've even tried to manage both classes as Object but without success.

Is that possible? If yes, what is the best approach to do it?

Many thanks
 
Last edited:

agraham

Expert
Licensed User
Longtime User
You could set Viewer as an Object then conditionally set and access it using a conditional and As(Type) but it's a bit clumsy. There may be a more elegant solution but I can't think of one at the moment so you might just as well have an instance of each viewer and access the appropriate one with conditionals keeping the other one uninitialized or not visible.
B4X:
Sub Globals
    
    Dim View As Object   
End Sub

Sub Activity_Create(FirstTime As Boolean)

    If Settings.UseViewer2D Then
        Dim G2D As GCodeViewer2D  ' Must be a global variable
       ' initialize G2D
       Viewer = G2D
    Else If Settings.UseViewer3D Then
        Dim G3D As GCodeViewer3D  ' Must be a global variable
       ' initialize G3D
       Viewer = G3D
    End If

End Sub


Sub Somewhere
    If Settings.UseViewer2D Then
       Viewer.As(GCodeViewer2D).DoSomething
    Else If Settings.UseViewer3D Then
       Viewer.As(GCodeViewer3D).DoSomething
    End If
End Sub
 
Upvote 0

max123

Active Member
Licensed User
Many thanks @agraham for your suggestions, but there are some problems with these,

- the first one (and most important) is that my code is a very long piece of code, about 15000 lines, and I've a lots of Viewer class method calls, replace all these is really problematic.
- the second one, I've an old B4A 7.80 the last that work on 32 bit system (my old system), I cannot upgrade this moment, so the only option I've is to use 7.80, and it do not support the As keyword that was introduced in new IDE releases.
- because it use a very big Canvas, like 5000x5000 pixels, this consume a lot of memory, I cannot instantiate both classess and just leave the one unused not visible, it consumes and is not used.

I need to find a better solution.

Many thanks
 
Last edited:
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
- the second one, I've an old B4A 7.80 the last that work on 32 bit system (my old system), I cannot upgrade this moment,
Why? New versions are free (unluckily, Erel ;)) and always compatible with the previous ones.
 
Upvote 0

max123

Active Member
Licensed User
Hi @LucaMs I know this, but is my hardware (and software) system that not support it, I develop on an old pc with Win XP 32 bit.

Have you some suggestions on my issue ?

Possible that other users never had this necessity to declare a class at runtime dinamically instead of declare it as static?

PS: I'm italian too ;)
 
Last edited:
Upvote 0

agraham

Expert
Licensed User
Longtime User
- the first one (and most important) is that my code is a very long piece of code, about 15000 lines, and I've a lots of Viewer class method calls, replace all these is really problematic.

Write a third class that holds instances for both viewers. Replicate in this class each common public method for the two viewers and select within each method the viewer instance to use. Make your Viewer global an instance of this third class. That way you don't have to change all the method calls in your code. This should also be reasonable efficient as your only extra overhead on each call is a conditional test.
 
Upvote 0

max123

Active Member
Licensed User
Write a third class that holds instances for both viewers. Replicate in this class each common public method for the two viewers and select within each method the viewer instance to use. Make your Viewer global an instance of this third class. That way you don't have to change all the method calls in your code. This should also be reasonable efficient as your only extra overhead on each call is a conditional test.

This may be temporally, but I think not the real solution, this class has to be very fast to process and draw, what I do is read a gcode file, extrapolate it's XYZ coordinates and draw as fast as possible. While it draw, my app send the gcode to a 3D printer (over USB OTG) that print. If the class draw slow I can see the printer slow down in curves when there are a lots of small segments, like eg. in a circle. Any millisecond is important here.

The gcode files that I process also reach 30 and more MB and reach 1,000,000 - 1,500,000 lines and even more, so it is important not to add any overhead, now it processes and draws about 100 gcode lines every second (in sumulation as fast possible reducing the canvas redrawn) and I am looking for it to optimize the speed as much as possible, and won't add any overhead.

NB: I went from Arduino, ESP8266, ESP32 old C++ school where in my oled and TFT libraries that I wrote I used 'inline' functions to reduce the time required to call recursive functions (microseconds, sometimes hundred of nanoseconds), so I start my projects with optimization in mind, sometime this happen, sometime not ;) but these libraries are capable to show videos read from SD, not just images. I started with 7 FPS with existing libraries and end up at 130+ FPS with my libraries after 4 years of optimizations.
 
Last edited:
Upvote 0

agraham

Expert
Licensed User
Longtime User
This may be temporally, but I think not the real solution,
It's as near as you will get! I assume that you mean "temporary". As I said above the performance hit should not be a lot unless the methods actually do very little work which I would have thought unlikely for graphics tasks..
 
Upvote 0

max123

Active Member
Licensed User
Yes, sorry I mean temporary....
Yes, some functions, the most important that need to be faster are Viewer.DrawLine and Viewer.DrawLineTo, these do not do much, just translate coordinates, swap Y and use a Canvas.DrawLine to draw the line.

I will try it as test to see performances if there are not other best ways.

Many thanks
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
these do not do much .. Canvas.DrawLine to draw the line.
That probably involves more CPU cycles, depending on the length of the line, than it might at first seem compared to the additional method call and simple conditional test. I would be surprised if the performance hit is very large.
 
Upvote 0

max123

Active Member
Licensed User
@agraham sorry, I do not know well, you intend to retain my two original classes and write a third that holds both like a sort of wrapper and then select here what class to use and call my classes method based on it, or you mean just to combine my two classes in one class (removing existing) and select here what to use ?
 
Last edited:
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
A possibility?

I did some experimentation (see .zip) It is possible to put the declaration of a Class in the Sub Globals instead of the Process_Globals.
Then one can use two Activities, one with Private Viewer As GCodeViewer2D and One with Private Viewer As GCodeViewer3D.

I don't know if that would help or not.
 

Attachments

  • ChooseApp.zip
    9.9 KB · Views: 35
Upvote 0

max123

Active Member
Licensed User
Also note that to draw as fast possible I added 2 sliders on thr app, one is the buffer size, and other is its multiplier, with these I can decide the number of lines to draw before make a view refresh wifh panel.Invalidate, this speed up a lot so I archive about 100 lines every second, by refreshing any line it process about ten lines every second
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
@agraham sorry, I do not know well, you intend to retain my two original classes and write a third that holds both like a sort of wrapper and then select here what class to use and call my classes method based on it, or you mean just to combine my two classes in one class (removing existing) and select here what to use ?
Keep your existing two classes as they are. Create a third class that has two private global variables, one for each viewer. In the new class create public subs with the same names as the public Subs in the two viewers - including Initialize, you did say they share the same method names and parameters. In each Sub test some global boolean to see which viewer should be in use and call the same named method on the appropriate private instance. This means you should not have to change any of your other code.
 
Upvote 0

max123

Active Member
Licensed User
A possibility?

I did some experimentation (see .zip) It is possible to put the declaration of a Class in the Sub Globals instead of the Process_Globals.
Then one can use two Activities, one with Private Viewer As GCodeViewer2D and One with Private Viewer As GCodeViewer3D.

I don't know if that would help

Many thanks both @agraham and @William Lancee for your precious help and time, now I'm not at PC, I will try both your solutions to know what fit best for my use case...

Both note that this is a part of a library that I wrote to write my 3D Printing, CNC Host app. I called it CNCFrame for B4A and jCNCFrame for B4J. This library have some classes that help the developer that want develop a gcode simulator, control a real machine over USB OTG, tested with 3DPrinter, CNC Router, Laser cutter machine, Foam cutter machine. The library have a GCodeLoader class to help open gcode files and read it line by line with just ReadNextLine command, GCodeParser class to help parse a line, extract codes and values, a SerialConnector panel, just add to Activity or Form, it will show serial USB devices, have a Refresh button, Connect Button, DTR, RTS, a baudrate selector, all builtin, just connect to the device, the viewers and more. I will release the library on the forum when finished. Really this library is in development from 2018
 
Last edited:
Upvote 0

max123

Active Member
Licensed User
Keep your existing two classes as they are. Create a third class that has two private global variables, one for each viewer. In the new class create public subs with the same names as the public Subs in the two viewers - including Initialize, you did say they share the same method names and parameters. In each Sub test some global boolean to see which viewer should be in use and call the same named method on the appropriate private instance. This means you should not have to change any of your other code.
So how I decide from main what class to use? Need to pass to a thirt class on every method as argument?
Or maybe it is convenient to write a Sub to select it like eg. ThirtClass.SelectViewer(ViewerType As String) ?
 
Upvote 0

cklester

Well-Known Member
Licensed User
Keep your existing two classes as they are. Create a third class that has two private global variables, one for each viewer. In the new class create public subs with the same names as the public Subs in the two viewers - including Initialize, you did say they share the same method names and parameters. In each Sub test some global boolean to see which viewer should be in use and call the same named method on the appropriate private instance. This means you should not have to change any of your other code.
I think you should also be able to put the objects in an array and just use an index, no conditionals required. Maybe?
 
Upvote 0
Top