## Switches!

Destructosoft
Posts: 88
Joined: Apr 03, 2011 3:44
Location: Inside the bomb
Contact:

### Switches!

I was frustrated at not having a "Bit" variable type, and didn't want byte switches, so I set up some switch routines.

Anyone with games that use a lot of switches (RPGs especially) might find this useful.

On the other hand, it won't save all that many bytes. :)

Code: Select all

`/'TestSwitches2Destructosoftv1.00020110304Tired of using dozens of byte variables as bit switches and wasting variable names (as well as7/8 of each byte)? Now you can consolidate them all as a bank of bit switches within the arraySWITCH(), using the following commands to access them:SGET(X) gets the value of switch X.SPUT(X[,Y]) puts a value of Y or 1 into switch X. The program example shows it more clearly.If you still wish names for certain switches, use ENUM.'/Declare Function sget(As Integer)As ByteDeclare Sub sput(As Integer,As Byte=1)Const numswitches=1000Const numbytes=numswitches\8+1Dim Shared As UByte switch(numbytes)Dim As Short t,uClsFor t=0 To numswitches If Rnd>.5 Then  sput(t)'activate switch Else  sput(t,0)'deactivate switch End IfNextFor t=1 To 30 u=Int(Rnd*(numswitches+1)) Print"Switch";u;sget(u)NextSleepEndFunction sget(t As Integer)As Byte'checks one of a number of switches and returns 0 or 1Dim As Integer u=t\8,v=2^(t Mod 8)Return (switch(u)And v)\vEnd FunctionSub sput(t As Integer,b As Byte=1)'put a value of 0 or 1 into an array of switchesDim As Integer u=t\8,v=2^(t Mod 8)If b=0 Then switch(u)-=(switch(u)And v)Else switch(u) Or= vEnd Sub`
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA
The mod operation makes indexing the bits relatively slow, and the byte operations slow the code further. And since your example doesn’t clearly demonstrate that the code works correctly (as it does), I coded a more reasonable test along with a pair of procedures done in inline assembly.

Code: Select all

`'=========================================================================dim shared as uinteger switch1(20000)dim shared as uinteger switch2(20000)'========================================================================='checks one of a number of switches and returns 0 or 1Function sget(t As Integer)As Byte    Dim As Integer u=t\8,v=2^(t Mod 8)    Return (switch1(u)And v)\vEnd Function'========================================================================='put a value of 0 or 1 into an array of switchesSub sput(t As Integer,b As Byte=1)  Dim As Integer u=t\8,v=2^(t Mod 8)  If b=0 Then switch1(u)-=(switch1(u)And v)Else switch1(u) Or= vEnd Sub'=========================================================================function sget_asm naked( bitIndex as integer ) as integer    asm        xor eax, eax          '' zero eax        mov ecx, [esp+4]      '' get bitIndex into ecx        shr ecx, 5            '' convert to dword index        lea edx, [switch2]    '' get address of element 0 into edx        mov edx, [edx+ecx*4]  '' get indexed dword into edx        mov ecx, [esp+4]      '' get bitIndex into ecx        bt  edx, ecx          '' copy indexed bit into carry flag        rcl eax, 1            '' rotate carry flag into bit 0 of eax        ret 4                 '' return and remove parameter from stack    end asmend function'=========================================================================sub sput_asm naked( bitIndex as integer, bitValue as integer )    asm        push ebx              '' preserve the value of ebx        mov eax, [esp+12]     '' get bitValue into eax        mov ecx, [esp+8]      '' get bitIndex into ecx        shr ecx, 5            '' convert ecx to dword index        lea ebx, [switch2]    '' get address of element 0 into ebx        mov edx, [ebx+ecx*4]  '' get indexed dword into edx        mov ecx, [esp+8]      '' get bitIndex into ecx        test eax, eax         '' test bitValue for zero        jz  0f                '' jump if zero        bts edx, ecx          '' set the indexed bit        jmp 1f      0:        btr edx, ecx          '' reset the indexed bit      1:        shr ecx, 5            '' convert ecx to dword index        mov [ebx+ecx*4], edx  '' copy new value back into array        pop ebx               '' restore the value of ebx        ret 8                 '' return and remove parameters from stack    end asmend sub'=========================================================================dim as double t1, t2dim as integer xfor i as integer = 0 to 199    x = int(rnd+0.5)    print i, x,    sput_asm( i, x )    print sget_asm( i ),    sput( i, x )    print sget( i )nextprintsleep 3000t1 = timerfor i as integer = 1 to 40000    x = int(rnd+0.5)nextt2 = timerprint using "#.###s"; t2-t1t1 = timerfor i as integer = 1 to 40000    x = int(rnd+0.5)    sput( i, x )    sget( i )nextt2 = timerprint using "#.###s"; t2-t1t1 = timerfor i as integer = 1 to 40000    x = int(rnd+0.5)    sput_asm( i, x )    sget_asm( i )nextt2 = timerprint using "#.###s"; t2-t1sleep`

Running on a P3, the assembly versions are ~17x faster (for the get/put pair and with the times adjusted for the randomization overhead).

Code: Select all

`0.011s0.079s0.015s390 cycles, sput439 cycles, sget30 cycles, sput_asm12 cycles, sget_asm`

Edit: added the cycle counts to the results.
Last edited by MichaelW on Apr 17, 2011 11:53, edited 1 time in total.
TJF
Posts: 3600
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:
I prefer to use a combination of UNION/TYPE like

Code: Select all

`UNION MySwitches  TYPE '        use this to set/reset a single switch    AS INTEGER Switch0 : 1 ' use one bit    AS INTEGER Switch1 : 1    AS INTEGER Switch2 : 1    AS INTEGER Switch3 : 1    AS INTEGER Switch4 : 1    AS INTEGER Switch5 : 3 ' use 3 bits (states 0 TO 7)  END TYPE  AS ULONGINT All ' use this to set/reset all at onceEND UNIONDIM AS MySwitches My' set all switches onMy.ALL = &b11111111' show one switch? My.Switch3' clear the switchMy.Switch3 = 0' show it again? My.Switch3`

You can name the switches like variables for easy access.

It can handle switches with 2 states. Or they can have a higher number of states like 4, 8, 16, .... Just specify the number of bits to use.

And it's fast.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA
TJF wrote:I prefer to use a combination of UNION/TYPE like…

Bit fields are fast, and have the benefit of names for each field, but the method is limited to 64 bits and is not suitable for the same applications as the OP’s code.
TJF
Posts: 3600
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:
MichaelW wrote:... the method is limited to 64 bits

Why? (The all-in-one access gets a bit more complicated if the number of bits is > 64.)

MichaelW wrote:... and is not suitable for the same applications as the OP’s code.

1000 switches into one unit is realy unusual (and hard to handle in the source code).

In most cases it makes sense to bundle a smaller number of switches in a unit (ie one unit for each player, one for each area, ...) and then bundle this units again (like a tree structure).
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA
TJF wrote:
MichaelW wrote:... the method is limited to 64 bits

Why? (The all-in-one access gets a bit more complicated if the number of bits is > 64.)

I should have specified “the method as posted”.

1000 switches into one unit is realy unusual (and hard to handle in the source code).

It seems that way to me, but nevertheless, bit fields are not suitable for the same applications.
TJF
Posts: 3600
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:
MichaelW wrote:
TJF wrote:
MichaelW wrote:... the method is limited to 64 bits

Why? (The all-in-one access gets a bit more complicated if the number of bits is > 64.)

I should have specified “the method as posted”.

This is wrong. In “the method as posted” a single bitfield can have more than 64 bits like

Code: Select all

`UNION MySwitches  TYPE '        use this to set/reset a single switch    AS UBYTE Switch00 : 1    AS UBYTE Switch01 : 1    AS UBYTE Switch02 : 1    AS UBYTE Switch03 : 1    AS UBYTE Switch04 : 1    AS UBYTE Switch05 : 1    AS UBYTE Switch06 : 1    AS UBYTE Switch07 : 1    AS UBYTE Switch08 : 1    AS UBYTE Switch09 : 1    AS UBYTE Switch10 : 1    AS UBYTE Switch11 : 1    AS UBYTE Switch12 : 1    AS UBYTE Switch13 : 1    AS UBYTE Switch14 : 1    AS UBYTE Switch15 : 1    AS UBYTE Switch16 : 1    AS UBYTE Switch17 : 1    AS UBYTE Switch18 : 1    AS UBYTE Switch19 : 1    AS UBYTE Switch20 : 1    AS UBYTE Switch21 : 1    AS UBYTE Switch22 : 1    AS UBYTE Switch23 : 1    AS UBYTE Switch24 : 1    AS UBYTE Switch25 : 1    AS UBYTE Switch26 : 1    AS UBYTE Switch27 : 1    AS UBYTE Switch28 : 1    AS UBYTE Switch29 : 1    AS UBYTE Switch30 : 1    AS UBYTE Switch31 : 1    AS UBYTE Switch32 : 1    AS UBYTE Switch33 : 1    AS UBYTE Switch34 : 1    AS UBYTE Switch35 : 1    AS UBYTE Switch36 : 1    AS UBYTE Switch37 : 1    AS UBYTE Switch38 : 1    AS UBYTE Switch39 : 1    AS UBYTE Switch40 : 1    AS UBYTE Switch41 : 1    AS UBYTE Switch42 : 1    AS UBYTE Switch43 : 1    AS UBYTE Switch44 : 1    AS UBYTE Switch45 : 1    AS UBYTE Switch46 : 1    AS UBYTE Switch47 : 1    AS UBYTE Switch48 : 1    AS UBYTE Switch49 : 1    AS UBYTE Switch50 : 1    AS UBYTE Switch51 : 1    AS UBYTE Switch52 : 1    AS UBYTE Switch53 : 1    AS UBYTE Switch54 : 1    AS UBYTE Switch55 : 1    AS UBYTE Switch56 : 1    AS UBYTE Switch57 : 1    AS UBYTE Switch58 : 1    AS UBYTE Switch59 : 1    AS UBYTE Switch60 : 1    AS UBYTE Switch61 : 1    AS UBYTE Switch62 : 1    AS UBYTE Switch63 : 1    AS UBYTE Switch64 : 1    AS UBYTE Switch65 : 1    AS UBYTE Switch66 : 1    AS UBYTE Switch67 : 1    AS UBYTE Switch68 : 1    AS UBYTE Switch69 : 1    AS UBYTE Switch70 : 1    AS UBYTE Switch71 : 1    AS UBYTE Switch72 : 1    AS UBYTE Switch73 : 1    AS UBYTE Switch74 : 1    AS UBYTE Switch75 : 1    AS UBYTE Switch76 : 1    AS UBYTE Switch77 : 1    AS UBYTE Switch78 : 1    AS UBYTE Switch79 : 1  END TYPE  AS STRING*10 All ' use this to set/reset all at onceEND UNIONDIM AS MySwitches My' set all switches onMy.ALL = STRING(10, &B11111111)' show one switch? My.Switch79' clear the switchMy.Switch79 = 0' show it again? My.Switch79`

But a big bitfield has the disadvantage that it's more difficult to set all switches at once (using STRING variables are most effective here).

But, it's not efficient to handle such a big bitfield in the source code. So, as you said it's better to spread them over smaller logical units (< 64 bits). All this logical units can get hosted in an UDT, if needed.
TJF
Posts: 3600
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:
MichaelW wrote:... bit fields are not suitable for the same applications.

BTW: If I'd need a large bitfield accessed by indices I would reduce global vars and use something like

Code: Select all

`#DEFINE SwitchesError(_V_) ?"Switches error: Index out of range (" & _V_ & ")"TYPE Switches  AS STRING Store  AS INTEGER Maxi  DECLARE CONSTRUCTOR(BYVAL N AS UINTEGER)  DECLARE PROPERTY Switch(BYVAL I AS UINTEGER, BYVAL B AS INTEGER)  DECLARE PROPERTY Switch(BYVAL I AS UINTEGER) AS INTEGEREND TYPECONSTRUCTOR Switches(BYVAL N AS UINTEGER)  Maxi = N SHR 3  Store = STRING(1 + Maxi, 0)END CONSTRUCTORPROPERTY Switches.Switch(BYVAL I AS UINTEGER) AS INTEGER  VAR p = I SHR 3  IF p > Maxi THEN SwitchesError(I) : RETURN 0  RETURN BIT(Store[p], I - (p SHL 3))END PROPERTYPROPERTY Switches.Switch(BYVAL I AS UINTEGER, BYVAL B AS INTEGER)  VAR p = I SHR 3, x = I - (p SHL 3)  IF p > Maxi THEN SwitchesError(I) : EXIT PROPERTY  IF B THEN Store[p] = BITSET(Store[p], x) : EXIT PROPERTY  Store[p] = BITRESET(Store[p], x)END PROPERTY`

Example (like the one in OP)

Code: Select all

`CONST Numswitches = 100DIM AS Switches Test = NumswitchesCLSFOR i AS INTEGER = 0 TO Numswitches  Test.Switch(i) = RND > .5NEXTFOR j AS INTEGER = 0 TO 30  VAR i = INT(RND * (Numswitches + 1))  PRINT"Switch"; i; Test.Switch(i)NEXTVAR i = 699PRINT"Switch"; i; Test.Switch(i)Test.Switch(i) = Test.Switch(i) XOR -1PRINT"Switch"; i; Test.Switch(i)#IFNDEF __FB_UNIX__SLEEP#ENDIF`

Last edited by TJF on Apr 17, 2011 18:19, edited 4 times in total.
Destructosoft
Posts: 88
Joined: Apr 03, 2011 3:44
Location: Inside the bomb
Contact:
Hmmm. Quite some interesting and innovative alternates to my methods.

Of course 1000 is a lot of switches, but such things are to be expected if you're keeping track of which events were triggered or which treasure chests were opened. I had 5000 switches in my last RPG Maker XP project.

I don't mind use of Mod and other "slowing" operations in the game I'm writing now, since it runs faster than it needs to anyway. But thanks for pointing that out.
TJF
Posts: 3600
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:
Destructosoft wrote:Of course 1000 is a lot of switches, but such things are to be expected ...

And they are to be expected in B/W images. That's why I made my second example.
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
You've just got a bit flag there. All it takes is some bitwise operators and named constants and it's as fast as it gets without using inline asm (which will only save you a cycle or two for having optimized memory access).

ie:

Code: Select all

`#define flag_one 1#define flag_two 2dim as uinteger   flag = 0flag Or= flag_oneasm   or dword Ptr [flag], flag_twoif flag and flag_one then print "bit 0 set"if flag and flag_two then print "bit 1 set"`

Edit:

You could also do a forum search for "bit array" and you have gotten these older deprecated projects and learned that fbc already has bit array manipulations.

fb docs: http://www.freebasic.net/wiki/wikka.php ... FunctIndex [see "Bit Manipulation"]
sir_mud
Posts: 1401
Joined: Jul 29, 2006 3:00
Location: US
Contact:
My bit array is part of the extended library, ext.freebasic.net
Dinosaur
Posts: 1357
Joined: Jul 24, 2005 1:13
Location: Hervey Bay
Contact:
Hi all

How does all this compare to the speed of Bit and BitSet commands.

Code: Select all

`                  If Bit(MyDouble,45) <> 0 Then`
or

Code: Select all

`UBW32.OutputA = BitSet(UBW32.OutputA,AlarmBit)`

Regards
TJF
Posts: 3600
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:
Have a look at the wiki pages. These are macros:

Code: Select all

`#define Bit( value, bit_number ) _  (((value) and (Cast(TypeOf(value), 1) shl (bit_number))) <> 0)#define Bitset( value, bit_number ) _  ((value) or (Cast(TypeOf(Value), 1) shl (bit_number)))#define Bitreset( value, bit_number ) _  ((value) and not (Cast(TypeOf(Value), 1) shl (bit_number)))`
Dinosaur
Posts: 1357
Joined: Jul 24, 2005 1:13
Location: Hervey Bay
Contact:
Hi all

Ok, so it's a macro, but is the below test a fair one, cause I get 0.002s on both Michael's asm test and this test.

Code: Select all

`t1 = TimerFor i As Integer = 1 To 40000    x = int(rnd+0.5)    If Bit(x,5) > 0 Then        x = BitReset(x,5)    Else        x = BitSet(x,5)    EndIfNextt2 = Timerprint using "#.###s"; t2-t1`
Regards