Switches!

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
Destructosoft
Posts: 88
Joined: Apr 03, 2011 3:44
Location: Inside the bomb
Contact:

Switches!

Post by Destructosoft »

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

/'TestSwitches2
Destructosoft
v1.000
20110304

Tired of using dozens of byte variables as bit switches and wasting variable names (as well as
7/8 of each byte)? Now you can consolidate them all as a bank of bit switches within the array
SWITCH(), 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 Byte
Declare Sub sput(As Integer,As Byte=1)

Const numswitches=1000
Const numbytes=numswitches\8+1
Dim Shared As UByte switch(numbytes)
Dim As Short t,u

Cls
For t=0 To numswitches
 If Rnd>.5 Then
  sput(t)'activate switch
 Else
  sput(t,0)'deactivate switch
 End If
Next
For t=1 To 30
 u=Int(Rnd*(numswitches+1))
 Print"Switch";u;sget(u)
Next
Sleep
End

Function sget(t As Integer)As Byte'checks one of a number of switches and returns 0 or 1
Dim As Integer u=t\8,v=2^(t Mod 8)
Return (switch(u)And v)\v
End Function

Sub sput(t As Integer,b As Byte=1)'put a value of 0 or 1 into an array of switches
Dim As Integer u=t\8,v=2^(t Mod 8)
If b=0 Then switch(u)-=(switch(u)And v)Else switch(u) Or= v
End Sub
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

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 1
Function sget(t As Integer)As Byte
    Dim As Integer u=t\8,v=2^(t Mod 8)
    Return (switch1(u)And v)\v
End Function

'=========================================================================

'put a value of 0 or 1 into an array of switches
Sub 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= v
End 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 asm
end 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 asm
end sub

'=========================================================================

dim as double t1, t2
dim as integer x

for 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 )
next
print

sleep 3000

t1 = timer
for i as integer = 1 to 40000
    x = int(rnd+0.5)
next
t2 = timer
print using "#.###s"; t2-t1

t1 = timer
for i as integer = 1 to 40000
    x = int(rnd+0.5)
    sput( i, x )
    sget( i )
next
t2 = timer
print using "#.###s"; t2-t1

t1 = timer
for i as integer = 1 to 40000
    x = int(rnd+0.5)
    sput_asm( i, x )
    sget_asm( i )
next
t2 = timer
print using "#.###s"; t2-t1

sleep
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.011s
0.079s
0.015s

390 cycles, sput
439 cycles, sget
30 cycles, sput_asm
12 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: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

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 once
END UNION

DIM AS MySwitches My

' set all switches on
My.ALL = &b11111111

' show one switch
? My.Switch3

' clear the switch
My.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

Post by MichaelW »

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: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

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

Post by MichaelW »

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: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

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 once
END UNION

DIM AS MySwitches My

' set all switches on
My.ALL = STRING(10, &B11111111)

' show one switch
? My.Switch79

' clear the switch
My.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: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

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 INTEGER
END TYPE

CONSTRUCTOR Switches(BYVAL N AS UINTEGER)
  Maxi = N SHR 3
  Store = STRING(1 + Maxi, 0)
END CONSTRUCTOR

PROPERTY 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 PROPERTY

PROPERTY 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 = 100
DIM AS Switches Test = Numswitches

CLS
FOR i AS INTEGER = 0 TO Numswitches
  Test.Switch(i) = RND > .5
NEXT

FOR j AS INTEGER = 0 TO 30
  VAR i = INT(RND * (Numswitches + 1))
  PRINT"Switch"; i; Test.Switch(i)
NEXT

VAR i = 699
PRINT"Switch"; i; Test.Switch(i)
Test.Switch(i) = Test.Switch(i) XOR -1
PRINT"Switch"; i; Test.Switch(i)

#IFNDEF __FB_UNIX__
SLEEP
#ENDIF
Edit: added some error handling.
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:

Post by Destructosoft »

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: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

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
Location: SK, Canada

Post by 1000101 »

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 2

dim as uinteger   flag = 0

flag Or= flag_one

asm   or dword Ptr [flag], flag_two

if 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.

sir_mud's thread: http://www.freebasic.net/forum/viewtopic.php?t=9198 [dead external link]
1000101's thread: http://www.freebasic.net/forum/viewtopic.php?t=1699 [dead external link]
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:

Post by sir_mud »

My bit array is part of the extended library, ext.freebasic.net
Dinosaur
Posts: 1481
Joined: Jul 24, 2005 1:13
Location: Hervey Bay (.au)

Post by Dinosaur »

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: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

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: 1481
Joined: Jul 24, 2005 1:13
Location: Hervey Bay (.au)

Post by Dinosaur »

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 = Timer
For 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)
    EndIf
Next
t2 = Timer
print using "#.###s"; t2-t1
Regards
Post Reply