Discussion:
[Gambas-user] Joystick interface class
Jean-Francois Perreault
2005-03-06 20:22:39 UTC
Permalink
I simplified my earlier joystick interface class
it is a lot cleaner now but I have a problem
I can't pass argument to the class constructor
when I quit I get errors and testing shows that the destructor is not
being called
also events don't fire

I guess most of my problems are related to the way I'm instanciating by
CJoystick class
is it really necessary that I run

cJoystick = NEW CJoystick("/dev/js0") AS cJoystick
in my FMain's constructor , that just sound sooo redundant since
I'm aiming for the lowest line count possible

I haven't tried re-instanciating cJoystick as program launch yet, but
also I'm curious are
the CJoystick class event properly declared , raised and used (used in
FMain) ?

Jean-Francois Perreault
Jean-Francois Perreault
2005-03-07 13:51:52 UTC
Permalink
I narrowed my problem down to this

I have a CJoystick class and my main form class FMain

in FMain I instanciate a CJoystick object as cJoystick1

PUBLIC cJoystick1 AS CJoystick

PUBLIC SUB cJoystick1_axis(bAxisNumber AS Byte,iValue AS Short,kIsInit
AS Boolean,iTimeStamp AS Integer)
PRINT "axis event fired !"
END

PUBLIC SUB Form_Open()
cJoystick1 = NEW CJoystick(FMain,"/dev/js0")
END

and my CJoystick class is like this

PUBLIC hJoystick AS File

PUBLIC SUB _new(hParent AS Object,sDevice AS String)
OPEN sDevice FOR READ WATCH BIG AS #hJoystick
'OPEN "/dev/js0" FOR READ WATCH BIG AS #hJoystick
object.attach(ME,hParent,"Joystick1")
END

PUBLIC SUB _free()
CLOSE #hJoystick
END

PUBLIC SUB File_Read() ' Here is what each of the 8 bytes of an event
mean
DIM b[8] AS Byte ' Byte 0 to 3 is the time stamp , least
significant byte first
DIM i AS Byte ' Byte 4 and 5 is the value , least
significant byte first
' Byte 6 is the event type flags bit 7 is the
INIT flag occurs when the device
' is first open or the stack overflowed
(maximum 64 events in the stack)
' Byte 7 is the number of the axis or button
for this event
FOR i = 0 TO 7
READ #hJoystick , b[i]
NEXT

IF b[6] AND JS_EVENT_AXIS THEN RAISE axis(b[7],b[4] + b[5] * 256,b[6]
AND JS_EVENT_INIT, b[0] + b[1] * 256 + b[2] * 512 + b[3] * 1024)
IF b[6] AND JS_EVENT_BUTTON THEN RAISE button(b[7],b[4] + b[5] *
256,b[6] AND JS_EVENT_INIT, b[0] + b[1] * 256 + b[2] * 512 + b[3] * 1024)
IF b[6] AND JS_EVENT_AXIS THEN print "File_Read fired"
END


the rest of the code in it's lastest version is at
http://domn.net/JoystickDemo.tar.gz

now this run with no error but the axis and and button event do not fire
in the FMain cJoystick1_axis sub
on the console I get File_Read fired but not axis event fired !

I'm pretty sure the problem is either how I declare cJoystick1 or
redeclare it in Form_Open
or how I'm using object.attach in CJoystick's _new
Benoit Minisini
2005-03-07 13:57:25 UTC
Permalink
Post by Jean-Francois Perreault
I narrowed my problem down to this
I have a CJoystick class and my main form class FMain
in FMain I instanciate a CJoystick object as cJoystick1
PUBLIC cJoystick1 AS CJoystick
PUBLIC SUB cJoystick1_axis(bAxisNumber AS Byte,iValue AS Short,kIsInit
AS Boolean,iTimeStamp AS Integer)
PRINT "axis event fired !"
END
PUBLIC SUB Form_Open()
cJoystick1 = NEW CJoystick(FMain,"/dev/js0")
END
and my CJoystick class is like this
PUBLIC hJoystick AS File
PUBLIC SUB _new(hParent AS Object,sDevice AS String)
OPEN sDevice FOR READ WATCH BIG AS #hJoystick
'OPEN "/dev/js0" FOR READ WATCH BIG AS #hJoystick
object.attach(ME,hParent,"Joystick1")
END
PUBLIC SUB _free()
CLOSE #hJoystick
END
PUBLIC SUB File_Read() ' Here is what each of the 8 bytes of an event
mean
DIM b[8] AS Byte ' Byte 0 to 3 is the time stamp , least
significant byte first
DIM i AS Byte ' Byte 4 and 5 is the value , least
significant byte first
' Byte 6 is the event type flags bit 7 is the
INIT flag occurs when the device
' is first open or the stack overflowed
(maximum 64 events in the stack)
' Byte 7 is the number of the axis or button
for this event
FOR i = 0 TO 7
READ #hJoystick , b[i]
NEXT
IF b[6] AND JS_EVENT_AXIS THEN RAISE axis(b[7],b[4] + b[5] * 256,b[6]
AND JS_EVENT_INIT, b[0] + b[1] * 256 + b[2] * 512 + b[3] * 1024)
IF b[6] AND JS_EVENT_BUTTON THEN RAISE button(b[7],b[4] + b[5] *
256,b[6] AND JS_EVENT_INIT, b[0] + b[1] * 256 + b[2] * 512 + b[3] * 1024)
IF b[6] AND JS_EVENT_AXIS THEN print "File_Read fired"
END
the rest of the code in it's lastest version is at
http://domn.net/JoystickDemo.tar.gz
now this run with no error but the axis and and button event do not fire
in the FMain cJoystick1_axis sub
on the console I get File_Read fired but not axis event fired !
I'm pretty sure the problem is either how I declare cJoystick1 or
redeclare it in Form_Open
or how I'm using object.attach in CJoystick's _new
Try to remove Object.Attach() in _new(), and to declare cJoystick this way:

cJoystick1 = NEW CJoystick(FMain,"/dev/js0") AS "Joystick1"

Tell me the result...
--
Benoit Minisini
mailto:***@users.sourceforge.net
Jean-Francois Perreault
2005-03-07 14:41:14 UTC
Permalink
hey that worked !!!
why ?

I even tried exactly
cJoystick1 = NEW CJoystick(FMain,"/dev/js0") AS cJoystick1
earlier

why the double quotes ?

... oh wait , now I remember that when I tried
cJoystick1 = NEW CJoystick(FMain,"/dev/js0") AS cJoystick1
the reason that didn't work was because it said

Type mismatch: wanted string, got Cjoystick instead
in fact that was just
cJoystick1 = NEW CJoystick("/dev/js0") AS cJoystick1
because that was before I tried passing the parent class
so I tought it meant it wanted as string for the constructor
sDevice parameter and that this gave that error because something
else was wrong

(I'm too used to having to reinterpret misleading error
message but congratulations that error message was dead on :) )


but still I don't get it , why does it want a string of the name of the
class instance ?
Post by Benoit Minisini
Post by Jean-Francois Perreault
I narrowed my problem down to this
I have a CJoystick class and my main form class FMain
in FMain I instanciate a CJoystick object as cJoystick1
PUBLIC cJoystick1 AS CJoystick
PUBLIC SUB cJoystick1_axis(bAxisNumber AS Byte,iValue AS Short,kIsInit
AS Boolean,iTimeStamp AS Integer)
PRINT "axis event fired !"
END
PUBLIC SUB Form_Open()
cJoystick1 = NEW CJoystick(FMain,"/dev/js0")
END
and my CJoystick class is like this
PUBLIC hJoystick AS File
PUBLIC SUB _new(hParent AS Object,sDevice AS String)
OPEN sDevice FOR READ WATCH BIG AS #hJoystick
'OPEN "/dev/js0" FOR READ WATCH BIG AS #hJoystick
object.attach(ME,hParent,"Joystick1")
END
PUBLIC SUB _free()
CLOSE #hJoystick
END
PUBLIC SUB File_Read() ' Here is what each of the 8 bytes of an event
mean
DIM b[8] AS Byte ' Byte 0 to 3 is the time stamp , least
significant byte first
DIM i AS Byte ' Byte 4 and 5 is the value , least
significant byte first
' Byte 6 is the event type flags bit 7 is the
INIT flag occurs when the device
' is first open or the stack overflowed
(maximum 64 events in the stack)
' Byte 7 is the number of the axis or button
for this event
FOR i = 0 TO 7
READ #hJoystick , b[i]
NEXT
IF b[6] AND JS_EVENT_AXIS THEN RAISE axis(b[7],b[4] + b[5] * 256,b[6]
AND JS_EVENT_INIT, b[0] + b[1] * 256 + b[2] * 512 + b[3] * 1024)
IF b[6] AND JS_EVENT_BUTTON THEN RAISE button(b[7],b[4] + b[5] *
256,b[6] AND JS_EVENT_INIT, b[0] + b[1] * 256 + b[2] * 512 + b[3] * 1024)
IF b[6] AND JS_EVENT_AXIS THEN print "File_Read fired"
END
the rest of the code in it's lastest version is at
http://domn.net/JoystickDemo.tar.gz
now this run with no error but the axis and and button event do not fire
in the FMain cJoystick1_axis sub
on the console I get File_Read fired but not axis event fired !
I'm pretty sure the problem is either how I declare cJoystick1 or
redeclare it in Form_Open
or how I'm using object.attach in CJoystick's _new
cJoystick1 = NEW CJoystick(FMain,"/dev/js0") AS "Joystick1"
Tell me the result...
Benoit Minisini
2005-03-07 14:48:19 UTC
Permalink
Post by Jean-Francois Perreault
hey that worked !!!
why ?
I even tried exactly
cJoystick1 = NEW CJoystick(FMain,"/dev/js0") AS cJoystick1
earlier
why the double quotes ?
... oh wait , now I remember that when I tried
cJoystick1 = NEW CJoystick(FMain,"/dev/js0") AS cJoystick1
the reason that didn't work was because it said
Type mismatch: wanted string, got Cjoystick instead
in fact that was just
cJoystick1 = NEW CJoystick("/dev/js0") AS cJoystick1
because that was before I tried passing the parent class
so I tought it meant it wanted as string for the constructor
sDevice parameter and that this gave that error because something
else was wrong
(I'm too used to having to reinterpret misleading error
message but congratulations that error message was dead on :) )
but still I don't get it , why does it want a string of the name of the
class instance ?
Maybe the documentation is not clear ? What comes after 'AS' in an
instanciation is the *event name* of the new object. You should use a string,
but if you put a variable, then the contents of the variable will be used as
event name. You were unlucky, as you used a object variable whose value is
NULL, and so was translated into a void string.

Maybe I should force the use of a string constant after the AS...

Regards,
--
Benoit Minisini
mailto:***@users.sourceforge.net
Rob
2005-03-07 16:12:53 UTC
Permalink
Post by Benoit Minisini
Maybe I should force the use of a string constant after the AS...
No, don't do that! I think someone just needs to update the
documentation to reflect that a string is required and not a class
name....

Rob
Jean-Francois Perreault
2005-03-07 16:14:53 UTC
Permalink
Hi,

ok , I tought about this some more and I understand why it needed a
string I see now how that
can be very useful to name the observer that way , I guess this is also
how groups are made

now that I can catch and properly interpret de joystick events
I want to optimise the procedure a bit
it's extremely minimalistic already so the only thing I can see I could
do to make it faster
is to fill the b array in only one read operation

a quick benchmark reveal that in normal operation the joystick will
easily generate 200
events with peaks up to 400 events per seconds

while doing all 8 reads only take between 0.00001 and 0.000024 seconds
to complete reducing
that by 8 could be useful to someone making a game (where input control
lag is intolerable)

is there a way I could fill the b array in one operation ?

right now the code is

dim b[7] as byte

FOR i = 0 TO 7
READ #hJoystick , b[i]
NEXT

I tried READ #jJoystick , b
but I get an error that READ wants a standard type , not an array
it doesn't sounds like it is possible , at least not with READ , but if
there's a way I'd like to hear it

also while doing this , I got the feeling I'm missing some events
so I tried to change the code to

dim b[7] as byte
WHILE NOT Eof(hJoystick)
FOR i = 0 TO 7
READ #hJoystick , b[i]
NEXT
WEND

but that doesn't work because I'm reading for a character device and it
cannot reach eof

I'm not sure I'm missing events because I'm not clear on how File_Read
gets called
if there is at least one readable byte in the file File_Read gets called
, but what if I don't read it all ?
does File_Read gets called as soon as the previous File_Read returns ?
(creating an endless loop if I don't read at least one byte)

next , to complete my joystick interface class I need to implement
something called ioctls
please wait I need to read up on that ... ;)

oh ok , trusty man ioctl tells me that ioctl is a function to control
character devices and probably other special files

it works like this you call it with 3 arguments the first is a valid
file descriptor
the second is a signed interger and the value is driver dependant
that second argument at the same time tells the command you want to
perform (the driver then knows wether this is a in or a out command)
also the size the data to input or output , the third argument is a
pointer to memory

since there's no pointers in gambas I guess this means I need a ioctl
interpreted gambas function which I suppose doesn't exist according to
the built-in doc ?

am I stuck ?

/me goes on to search how you guys managed to use the serial port if you
can't ioctl it
......

ok searching the mailing list the only match on ioctl I found is a very
old ( [Gambas-devel] Parallel port on 02/06/2003) thread I replied to (
off course I completely missed the point back then ;) )

so I guess only a component can do this ...

this is a bit off topic I was just wondering if it was possible to get
the pointer of a variable like in vb6 ?
I know this is totally bad hackery that shouldn't ever be used but it
could be a work around for things like this

this is stuff I learned reading visual basic hardcore a long while ago
http://vb.mvps.org/hardcore/
in vb you can use pointers , when you do an api call since variables are
passed byref the pointer gets passed by default
you also have CopyMemory, VarPtr, StrPtr, and ObjPtr (oh and AddressOf
for the pointer to SUBs , a must for using Callbacks) (I think
CopyMemory at least is another api call and that the real name is
RtlCopyMemory)

http://vb.mvps.org/hardcore/html/bringyourhatchet.htm

copymemory is fun to do stuff like
READ #hJoystick , mysignedinteger
CopyMemory(VarPtr(mysignedinteger) + 2,thethirdbyte,1)

I guess these might not work because there is no garantee how
variable are actually stored in memory and how is the interpreter going
to react if the variables change without it's knowledge , like would
happen if you used CopyMemory

but still while there are no garantee this could work , it just might
(for the current version anyway) that's why , at least for vb6 those
functions are undocumented and I think it could be fun to have those
function in gambas , just don't document them , don't support then and
warn anyone who use them that they could break anytime

... well now I am really off-topic , I'm just throwing this in the wind
tell me what you think and I'm going to resume working on my current
problem (ioctls)

...


oh btw , about the event I mostly got confused because the AS keyword
has multiple uses apparently and the documentation isn't very clear on
making the distinction

I'm not sure it's necessary to force a string constant for event
observer name ? maybe this could be useful in some unseen way ? I mean ,
if it doesn't get in the way other than getting noobs like me confused
.. maybe just spelling out how this all works better in the doc will be
enough .. in fact maybe is it explained properly somewhere in the docs
and I just haven't found where ?

anyway .. I'm going to publish this class to gambasforge when it's done
so it will server also as a good example on how to use custom class' events

-Jean-Francois Perreault
Benoit Minisini
2005-03-07 16:25:29 UTC
Permalink
Post by Jean-Francois Perreault
Hi,
ok , I tought about this some more and I understand why it needed a
string I see now how that
can be very useful to name the observer that way , I guess this is also
how groups are made
now that I can catch and properly interpret de joystick events
I want to optimise the procedure a bit
it's extremely minimalistic already so the only thing I can see I could
do to make it faster
is to fill the b array in only one read operation
a quick benchmark reveal that in normal operation the joystick will
easily generate 200
events with peaks up to 400 events per seconds
while doing all 8 reads only take between 0.00001 and 0.000024 seconds
to complete reducing
that by 8 could be useful to someone making a game (where input control
lag is intolerable)
is there a way I could fill the b array in one operation ?
right now the code is
dim b[7] as byte
FOR i = 0 TO 7
READ #hJoystick , b[i]
NEXT
I tried READ #jJoystick , b
but I get an error that READ wants a standard type , not an array
it doesn't sounds like it is possible , at least not with READ , but if
there's a way I'd like to hear it
You can use a dynamic array: DIM b AS NEW Byte[](8)
and use the b.Read() method.
Post by Jean-Francois Perreault
also while doing this , I got the feeling I'm missing some events
so I tried to change the code to
dim b[7] as byte
WHILE NOT Eof(hJoystick)
FOR i = 0 TO 7
READ #hJoystick , b[i]
NEXT
WEND
but that doesn't work because I'm reading for a character device and it
cannot reach eof
I'm not sure I'm missing events because I'm not clear on how File_Read
gets called
if there is at least one readable byte in the file File_Read gets called
, but what if I don't read it all ?
does File_Read gets called as soon as the previous File_Read returns ?
(creating an endless loop if I don't read at least one byte)
File_Read is called if there is something to read on the opened file.
Post by Jean-Francois Perreault
next , to complete my joystick interface class I need to implement
something called ioctls
please wait I need to read up on that ... ;)
You can't do ioctl() with Gambas at the moment.
Post by Jean-Francois Perreault
oh ok , trusty man ioctl tells me that ioctl is a function to control
character devices and probably other special files
it works like this you call it with 3 arguments the first is a valid
file descriptor
the second is a signed interger and the value is driver dependant
that second argument at the same time tells the command you want to
perform (the driver then knows wether this is a in or a out command)
also the size the data to input or output , the third argument is a
pointer to memory
since there's no pointers in gambas I guess this means I need a ioctl
interpreted gambas function which I suppose doesn't exist according to
the built-in doc ?
am I stuck ?
/me goes on to search how you guys managed to use the serial port if you
can't ioctl it
......
ok searching the mailing list the only match on ioctl I found is a very
old ( [Gambas-devel] Parallel port on 02/06/2003) thread I replied to (
off course I completely missed the point back then ;) )
so I guess only a component can do this ...
this is a bit off topic I was just wondering if it was possible to get
the pointer of a variable like in vb6 ?
I know this is totally bad hackery that shouldn't ever be used but it
could be a work around for things like this
this is stuff I learned reading visual basic hardcore a long while ago
http://vb.mvps.org/hardcore/
in vb you can use pointers , when you do an api call since variables are
passed byref the pointer gets passed by default
you also have CopyMemory, VarPtr, StrPtr, and ObjPtr (oh and AddressOf
for the pointer to SUBs , a must for using Callbacks) (I think
CopyMemory at least is another api call and that the real name is
RtlCopyMemory)
http://vb.mvps.org/hardcore/html/bringyourhatchet.htm
copymemory is fun to do stuff like
READ #hJoystick , mysignedinteger
CopyMemory(VarPtr(mysignedinteger) + 2,thethirdbyte,1)
I guess these might not work because there is no garantee how
variable are actually stored in memory and how is the interpreter going
to react if the variables change without it's knowledge , like would
happen if you used CopyMemory
but still while there are no garantee this could work , it just might
(for the current version anyway) that's why , at least for vb6 those
functions are undocumented and I think it could be fun to have those
function in gambas , just don't document them , don't support then and
warn anyone who use them that they could break anytime
... well now I am really off-topic , I'm just throwing this in the wind
tell me what you think and I'm going to resume working on my current
problem (ioctls)
...
oh btw , about the event I mostly got confused because the AS keyword
has multiple uses apparently and the documentation isn't very clear on
making the distinction
I'm not sure it's necessary to force a string constant for event
observer name ? maybe this could be useful in some unseen way ? I mean ,
if it doesn't get in the way other than getting noobs like me confused
.. maybe just spelling out how this all works better in the doc will be
enough .. in fact maybe is it explained properly somewhere in the docs
and I just haven't found where ?
anyway .. I'm going to publish this class to gambasforge when it's done
so it will server also as a good example on how to use custom class' events
-Jean-Francois Perreault
Components are written in C/C++ at the moment, so they can do what they want.
A component to get joystick event should be written in C, not in Gambas,
because of the ioctl() stuff, and because of the number of events generated.
Note that the sdl component will have joystick management too in it, thanks
to the sdl library.

Regards,
--
Benoit Minisini
mailto:***@users.sourceforge.net
Jean-Francois Perreault
2005-03-07 17:43:02 UTC
Permalink
Post by Benoit Minisini
You can use a dynamic array: DIM b AS NEW Byte[](8)
and use the b.Read() method.
but aren't those dynamic array much slower to read/write than native
arrays ?

yes and no I just run a quick benchmark while the execution is almost
1000% quicker (0.000001 instead of the previous 0.000024)
but the cpu usage was less 0.01% previously when treating 300 events per
seconds
it is now around 20% when treating about 70 events per seconds and over
60% when doing around 250 events per second

while latency went down , cpu usage went way up maybe if I put a wait
0.00001 in there ....

(as a side note DIM b AS NEW Byte[](8) gives me a syntax error , but DIM
b AS NEW Byte[] does not
so I also b.reside(8) , but I guess DIM b AS NEW Byte[](8) should work
because when I type
DIM b AS NEW Byte[]( there's a tooltip that appears and say "Byte[] (
[Size AS Interger ] )"
I'm running 1.0.2 and I recall reading something in the 1.0.3 changelog
about that ...
oh there it is
*1.0.2 - 12 Jan 2005*
*Interpreter*

* BUG: The [] operator has been fixed for Byte and Short arrays)
Post by Benoit Minisini
File_Read is called if there is something to read on the opened file.
you mean as soon as file_read terminate if there is still something to
read it will fire again immediately (or after a few milliseconds if it
is being polled?)
Post by Benoit Minisini
Post by Jean-Francois Perreault
next , to complete my joystick interface class I need to implement
something called ioctls
please wait I need to read up on that ... ;)
You can't do ioctl() with Gambas at the moment.
can there be a simple pass-through module ?

like a ioctl function that would be like this

ioctl (File AS File , iRequestCode AS Integer , bContent AS Byte[],
kForInput as Boolean)

kForInput would be necessary since gambas cannot know if the
driver-dependant iRequestCode is for input or output

bContent could only accept a native byte array , I guess that would make
the component simpler ,
but I don't know how you can pass an entire native byte array as a
parrameter ?!

so the component would call ioctl with iRequestCode as second parameter
"as is" and pass an intermediary byte array with as many byte as there
are elements in bContent and if kForInput is true then after ioctl is
called fill bContent with the content of the buffer byte array and if
kForInput is false when fill the buffer byte array with the content of
bContent and pass it to ioctl

but it the ioctl user gets iRequestCode then if kForInput is false ioctl
could overflow the internal buffer byte array to there should be some
security for that (make the buffer byte array never less than 256 bytes
maybe ? the thing is that gambas can't keep a table of all the known
request code because that would be too much trouble to maintain and it
would never have the request code you need for dealing with the
never-seen-before driver)

I suppose that you could get rid of kForInput if you always pass a big
enough buffer byte array and determine if any bits changed in the array
after calling ioctl you would then know if it was for in or out , except
if it was for out and the driver didn't change anything
Post by Benoit Minisini
Components are written in C/C++ at the moment, so they can do what they want.
A component to get joystick event should be written in C, not in Gambas,
because of the ioctl() stuff, and because of the number of events generated.
Note that the sdl component will have joystick management too in it, thanks
to the sdl library.
well actually now my joystick class is fast and usable , the ioctl
aren't that necessary , I just wanted to implement them for completeness
but all they are is


JSIOCGAXES get the number of axes (you can already determine
that by listening to INIT events ,
JSIOCGBUTTONS each axis and each button generate a INIT event at
initialisation)
JSIOCGNAME(len) get the Name string of the joystick (you can get it
with lsusb and dmesg but I consider that unclean)
JSIOCGVERSION get the driver version (not actually useful ,
except for reusing to deal with
pre-version 1 interfaces , you could guess that
with the kernel version anyway)

JSIOCSCORR those two last are special , they serve to
calibrate the joystick (which should )
JSIOCGCORR not be needed , the joystick should be
autocalibrated by the driver and the interface
for it is a bit strange , the joystick-api
documentation says that those two request
code can change from version to version and only
the application jscal , that come from
the same guys who made the joystick driver is
garanteed to work with this , but I would
have had included it too for completeness

also in my previous message , about the pointers and all , do you think
any of it makes sense ?
just a copymemory-alike function could be useful to deal with endianess
and other bitwise operations
don't you think ? (but if you say I'm completely ludicrous just to talk
about such a thing I'd believe you at this point ;) )


-Jean-Francois Perreault

Loading...