# [vJASS] SyncInteger

Discussion in 'JASS Resources' started by TriggerHappy, Apr 30, 2016.

### Code Moderator

Joined:
Jun 23, 2007
Messages:
3,665
Resources:
22
Spells:
11
Tutorials:
2
JASS:
9
Resources:
22
Updated. I tried base 20 but nothing changed.

I noticed in the current implementation some 8-10 digit numbers work but most don't.

For example 2147483647 will return 2147483520.

Code (Text):

1.0.5

[SyncInteger]
- OnSyncInteger returns trigger condition (for use with RemoveSyncEvent)
- Other minor changes

2. ### Troll-Brain

Joined:
Apr 27, 2008
Messages:
2,372
Resources:
1
JASS:
1
Resources:
1
Ok, time to debug then, i suppose the math fail somewhere.

Joined:
Jun 23, 2007
Messages:
3,665
Resources:
22
Spells:
11
Tutorials:
2
JASS:
9
Resources:
22
4. ### Nestharus

Joined:
Jul 10, 2007
Messages:
6,149
Resources:
8
Spells:
3
Tutorials:
4
JASS:
1
Resources:
8
I would think that you would want to synchronize 12 bits at a time using base 2. If a unit is present, it is a 1. If it isnt, it is a 0. Every unit would represent a position in a bitstring of 12 bits.

Here is the idea. 13 units

111111111111
EOF

You would synchronize from the highest bit down to the lowest bit. When a higher bit comes in, the next bitstring would be worked on. When EOF comes in, the stream would end. You would only select a unit if the 1 is present in the bitstring. 0's would be the absence of a synchronized unit. You could build the number by just adding the values together that the unit's represent. For example, if you were to synchronize the unit in the 3rd position, 2nd position, and 1st position, it would be 2^2 + 2^1 + 2^0, or 7.

I am again 100% unsure of how you are currently doing it, but I saw a base 20 there and figured I might put in some feedback ^_^.

For the file, you could use base 64 to rapidly combine the bits together.

5. ### Troll-Brain

Joined:
Apr 27, 2008
Messages:
2,372
Resources:
1
JASS:
1
Resources:
1
@TH : Yep i realized that also (after reading the very same thread yesterday), so instead of pow using an array of integer maybe ?
This way no real will be involved.

Also, what about making the base configurable with some constant integer ?
I think it should be up to the user to decide how many dummies he wants, the more, the fastest sync you will get.
Anyway that seems a library for a personal stuff, not in a public resource, right ?

@Nestharus :
You can sync as many booleans as you want if you convert it to integers.
Because of jass integer range/implementation, 32 booleans seems fair enough.
In fact i would ask TH to implement a such feature when there were no more bugs, you just ask before me.

Last edited: May 10, 2016
6. ### Nestharus

Joined:
Jul 10, 2007
Messages:
6,149
Resources:
8
Spells:
3
Tutorials:
4
JASS:
1
Resources:
8
@Troll-Brain
The idea was to synchronize integers using the method I described. Converting them to binary and synchronizing 12-bits at a time seems like the fastest way to sync integers using SelectUnit.

edit
Dr Super Good had a good idea. use 33 units, 32 for bits and 33rd for EOF. ALWAYS select 12 units, encoding as many 1's as you possibly can : ). 0's are passive, so this would allow you to encode up to 32 bits depending on the number of 1's ^_^.

Last edited: May 10, 2016
7. ### Troll-Brain

Joined:
Apr 27, 2008
Messages:
2,372
Resources:
1
JASS:
1
Resources:
1

Less selection = faster sync.
Higher base = less selections.

8. ### Nestharus

Joined:
Jul 10, 2007
Messages:
6,149
Resources:
8
Spells:
3
Tutorials:
4
JASS:
1
Resources:
8
Ah, I see now.

Thanks Troll-Brain.

I would use base 2, 4, 8, 16, 32, or 64 as bits can be packed into those.

So, from my understanding of the code then, SelectUnit is not asynchronous : ).

9. ### Troll-Brain

Joined:
Apr 27, 2008
Messages:
2,372
Resources:
1
JASS:
1
Resources:
1
I don't think the efficiency of the script or even logic really does matter here, since it's not in the same scale of the delay to sync anyway.
The time to sync is about 0.2 s in a lan game with 10 selections (or something like that).
I mean, the main problem is the time to sync. And we should focus on that first.

I don't think TH has even tested on battle.net. (i had the idea way before and i didn't care to test)

We could also imagine use these dummies for something else, instead of just wasting resources.

Problem is that they need to be selectable, so the use is limited.
Or maybe not that much, i've already read that some animations makes an unit not selectable (some property inside the model, but i don't have any knowledge about modeling)
However i don't know if it's with the mouse AND with a trigger action.
If it's only with the mouse, then it's perfect.

EDIT :

Well, it makes sense that you can select an unit in a local block and that the select event still fire on each computer.
You do that every day with your mouse.

### Code Moderator

Joined:
Jun 23, 2007
Messages:
3,665
Resources:
22
Spells:
11
Tutorials:
2
JASS:
9
Resources:
22
Not the current implementation but I have tested up to 64 selections and it was sent in under a second.

I have tested with 2-5 players.

Yes it is.

-------

I will be updating this soon enough

11. ### Nestharus

Joined:
Jul 10, 2007
Messages:
6,149
Resources:
8
Spells:
3
Tutorials:
4
JASS:
1
Resources:
8
Ok, so I wrote a Network 2 using SelectUnit instead of a gamecache and decided to benchmark it as compared to the original Network.

Keep in mind that original Network has the capacity to synchronize up to 32 bits of data at a time.

The original network did 5000 synchronizations in 34.625 seconds, sending across 19.53125 kilobytes of data.

The SelectUnit version did 5000 synchronizations in 122.25 seconds, sending across 3.662109375 kilobytes of data.

I think that this is not the droid we are looking for : ).

Now, how do these times change as more players go into the game? The SelectUnit may be more scalable. However, I still believe that if TriggerSyncReady is optimized in the gamecache algorithm, that the gamecache algorithm will crush the SelectUnit algorithm in all cases.

SelectUnit allows 512 synchronizations before throttling occurs, at which point only 300 selections may occur every half a second.

In comparison, gamecache allows 1536 synchronizations to occur immediately before throttling, at which point it is throttled to 512 synchronizations per second. This is with the current TriggerSyncReady spam.

I would like to compare gamecache algorithm with optimized syncs to SelectUnit in a 12-player setting across 5000 synchronizations.

Last edited: May 11, 2016
12. ### Zwiebelchen

Joined:
Sep 17, 2009
Messages:
6,791
Resources:
12
Models:
5
Maps:
1
Spells:
1
Tutorials:
1
JASS:
4
Resources:
12
All these values are completely theoretical and FAR OFF the reality when actually applied to maps.

So far, I had WAY BETTER results with selectUnit in practical Bnet tests (6 players) than I ever had with Network.

13. ### Nestharus

Joined:
Jul 10, 2007
Messages:
6,149
Resources:
8
Spells:
3
Tutorials:
4
JASS:
1
Resources:
8
Give me a moment because I have an interesting idea. I will look for testers later : ).

edit
IcemanBo, Killcide, 1 random pub, and I tested the new algorithm utilizing gamecache. Synchronizing 5000 values took 31 seconds. That is 19.53125 kilobytes of data : ).

I believe this is far faster than anything you could hope for with SelectUnit ^_^.

Rather than TriggerSyncReady spam, a periodic timer and polling was utilized.

Need to test with 12 players.

Last edited: May 11, 2016

### Code Moderator

Joined:
Jun 23, 2007
Messages:
3,665
Resources:
22
Spells:
11
Tutorials:
2
JASS:
9
Resources:
22
Here is some basic test code for GC in case some are wondering.

Code (vJASS):

scope TestInt initializer Init

globals
private timer Timer=CreateTimer()
private gamecache GC
endglobals

private function Check takes nothing returns nothing
local integer v

set v = GetStoredInteger(GC, "m", "1")

if (v > 0) then
// this runs for the first player before everyone else, may cause desync
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Took: " + R2S(TimerGetElapsed(Timer)) + " seconds, int was " + I2S(v))
call PauseTimer(GetExpiredTimer())
endif
endfunction

private function MapStart takes nothing returns nothing
local integer i = 1
local integer base = 0

if (GetLocalPlayer() == Player(0)) then
set base = 500
endif

call TimerStart(Timer, 512, false, null)

call StoreInteger(GC, "m", "1", base)
call SyncStoredInteger(GC, "m", "1")
endfunction

private function Init takes nothing returns nothing
set GC=InitGameCache("test.w3v")
call TimerStart(Timer, 1, false, function MapStart)
call TimerStart(CreateTimer(), 0.01, true, function Check)
endfunction

endscope

Depending on further tests the selection implementation may no longer be needed.

EDIT: just posting my progress before I go to sleep

I got syncing with GC to work (in LAN, 2 players, so who knows..), but it requires the SyncInteger library to notify when the GC sync is over for everyone.

Here is some sloppy test code

Code (vJASS):

scope TestInt2 initializer Init // requires SyncInteger

globals
private timer Timer=CreateTimer()
private gamecache GC
private boolean SyncComplete = false
private boolean SawMsg=false
private constant integer COUNT = 500
private integer Progress
endglobals

private function GCComplete takes nothing returns boolean
set SyncComplete = (GetSyncedInteger() == 1)
return false
endfunction

private function Check takes nothing returns nothing
local integer v //= GetStoredInteger(GC, "0", I2S(1))
local integer i = 0
local integer c = 0

//check
set i = 1
set v = 1
loop
exitwhen i > COUNT or v == 0
if (GetStoredInteger(GC, "0", I2S(i)) != i) then
//call BJDebugMsg("Fail @ " + I2S(i))
set v = 0
else
set c = c + 1
endif
set i = i + 1
endloop

if (v > 0) then

set v = GetStoredInteger(GC, "0", I2S(1))

if (not SawMsg) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "LOCAL: " + R2S(TimerGetElapsed(Timer)) + " seconds, int was " + I2S(v))
set SawMsg=true
endif

if (GetLocalPlayer() == Player(1)) then
set i = 1
endif
call SyncInteger(1, i)
endif

// this runs synchronously for everyone (:
if (SyncComplete) then
call PauseTimer(GetExpiredTimer())

call BJDebugMsg("SyncComplete")
call BJDebugMsg("Took: " + R2S(TimerGetElapsed(Timer)) + " seconds to sync " + I2S(COUNT) + " integers\nFirst: " + I2S(GetStoredInteger(GC, "0", "1")) + "\nLast: " + I2S(GetStoredInteger(GC, "0", I2S(COUNT))))
elseif (c != Progress) then
call BJDebugMsg(I2S(c) + "/" + I2S(COUNT))
set Progress=c
endif
endfunction

private function MapStart takes nothing returns nothing
local integer i = 1
local integer base = 0
local player p = Player(0)
local string mkey = I2S(GetPlayerId(p))

call TimerStart(Timer, 99999, false, null)

loop
exitwhen i > COUNT
if (GetLocalPlayer() == p) then
call StoreInteger(GC, mkey, I2S(i), base + i)
call SyncStoredInteger(GC, mkey, I2S(i))
endif
set i = i + 1
endloop
endfunction

private function Init takes nothing returns nothing
set GC=InitGameCache("test.w3v")
call TimerStart(Timer, 1, false, function MapStart)
call TimerStart(CreateTimer(), 0.01, true, function Check)
call OnSyncInteger(function GCComplete)
endfunction

endscope

EDIT2: working on a pub library

Last edited: May 12, 2016
15. ### Troll-Brain

Joined:
Apr 27, 2008
Messages:
2,372
Resources:
1
JASS:
1
Resources:
1
I know the truth.

You have changed the code, just because you couldn't handle the math bug

### Code Moderator

Joined:
Jun 23, 2007
Messages:
3,665
Resources:
22
Spells:
11
Tutorials:
2
JASS:
9
Resources:
22
haha except the new library requires the old one

I still need to fix it

EDIT:

Code (vJASS):

//callback handled internally for now

local SyncData d = SyncData.create(Player(0))
local integer i = 1

loop
exitwhen i > 1000
set i = i + 1
endloop

call d.start()

Last edited: May 13, 2016
17. ### edo494

Joined:
Apr 16, 2012
Messages:
3,855
Resources:
5
Spells:
1
JASS:
4
Resources:
5
what is GC supposed to mean? cause only think I can think of is Garbage collector, but that makes no sense in this case

18. ### Troll-Brain

Joined:
Apr 27, 2008
Messages:
2,372
Resources:
1
JASS:
1
Resources:
1
Game cache, obvioulsy

### Code Moderator

Joined:
Jun 23, 2007
Messages:
3,665
Resources:
22
Spells:
11
Tutorials:
2
JASS:
9
Resources:
22
Updated.

Code (Text):

v1.0.6

- Base is configurable
- Minor optimizations

I removed SyncString since my new implementation with GC is much better. Now a player can sync multiple strings at the same time, and there's improved performance.

Check out the new library !

https://www.hiveworkshop.com/posts/2824912/

20. ### Troll-Brain

Joined:
Apr 27, 2008
Messages:
2,372
Resources:
1
JASS:
1
Resources:
1
Seems to work like a charm :

Code (vJASS):
library SyncInteger initializer Init uses optional UnitDex, optional GroupUtils
/***************************************************************
*
*   v1.0.6, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
*   This library allows you to send integers to all other players.
*
*   _________________________________________________________________________
*   1. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Copy the script to your map and save it (requires JassHelper *or* JNGP)
*   _________________________________________________________________________
*   2. How it works
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Creates 12 dummy units and assigns 10 of them an integer from 0-9.
*          The 11th dummy is used to signal when the sequence of numbers is over.
*          The 12h signifies a negative number.
*
*       2. Breaks down the number you want to sync to one or more base 10 integers,
*          then selects each unit assoicated with that integer.
*
*       4. The selection event fires for all players when the selection has been sycned
*
*       5. The ID of the selected unit is one of the base 10 numbers. The current
*          total (starts at 0) is multiplied by 10 and the latest synced integer is
*          added to that. The process will repeat until it selects the 11th dummy,
*          and the total is our result.
*   _________________________________________________________________________
*   3. Proper Usage
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       - Avoid the SyncSelections native. It may cause the
*         thread to hang or make some units un-able to move.
*
*       - Dummies must be select-able (no locust)
*
*       - Run the script in debug mode while testing
*   _________________________________________________________________________
*   4. Function API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       function SyncInteger takes integer playerId, integer number returns boolean
*
*       function GetSyncedInteger takes nothing returns integer
*       function GetSyncedPlayer takes nothing returns player
*       function GetSyncedPlayerId takes nothing returns integer
*       function IsPlayerSyncing takes player p returns boolean
*       function IsSyncEnabled takes nothing returns boolean
*       function SyncIntegerToggle takes boolean flag returns nothing
*       function SyncIntegerEnable takes nothing returns nothing
*       function SyncIntegerDisable takes nothing returns nothing
*
*       function OnSyncInteger takes code func returns triggercondition
*       function RemoveSyncEvent takes triggercondition action returns nothing
*       function TriggerRegisterSyncEvent takes trigger t, integer eventtype returns nothing
*
*       function SyncInitialize takes nothing returns nothing
*       function SyncTerminate takes boolean destroyEvent returns nothing
*
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   -http://www.hiveworkshop.com/forums/submissions-414/syncinteger-syncstring-278674/
*
*/

globals
// calls SyncInitialize automatically
private constant boolean AUTO_INIT          = true

// owner of the dummy units
private constant player DUMMY_PLAYER        = Player(PLAYER_NEUTRAL_PASSIVE)

// dummy can *not* have locust (must be selectabe)
// basically anything should work (like 'hfoo')
private constant integer DUMMY_ID           = 'hfoo' // XE_DUMMY_UNITID

// dummy ghost ability
private constant integer DUMMY_ABILITY      = 'Aeth'

// debug mode
private constant boolean ALLOW_DEBUGGING    = true

// higher == more dummies but faster
private constant integer BASE               = 10

// don't need to change this
private constant integer DUMMY_COUNT        = BASE+2

// endconfig
constant integer EVENT_SYNC_INTEGER = 1

private trigger OnSelectTrigger = CreateTrigger()
private trigger EventTrig       = CreateTrigger()
private real FireEvent          = 0

private group SelectionGroup

private integer array SyncData
private integer LastPlayer
private integer LastSync
private unit array SyncIntegerDummy
private integer array Power
endglobals

function GetSyncedInteger takes nothing returns integer
return LastSync
endfunction

function GetSyncedPlayer takes nothing returns player
return Player(LastPlayer)
endfunction

function GetSyncedPlayerId takes nothing returns integer
return LastPlayer
endfunction

function IsPlayerSyncing takes player p returns boolean
return (SyncData[GetPlayerId(p)] != -1)
endfunction

function IsPlayerIdSyncing takes integer pid returns boolean
return (SyncData[pid] != -1)
endfunction

function IsSyncEnabled takes nothing returns boolean
return IsTriggerEnabled(OnSelectTrigger)
endfunction

function SyncIntegerEnable takes nothing returns nothing
call EnableTrigger(OnSelectTrigger)
endfunction

function SyncIntegerDisable takes nothing returns nothing
call DisableTrigger(OnSelectTrigger)
endfunction

function SyncIntegerToggle takes boolean flag returns nothing
if (flag) then
call EnableTrigger(OnSelectTrigger)
else
call DisableTrigger(OnSelectTrigger)
endif
endfunction

function OnSyncInteger takes code func returns triggercondition
endfunction

function RemoveSyncEvent takes triggercondition action returns nothing
call TriggerRemoveCondition(EventTrig, action)
endfunction

function TriggerRegisterSyncEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PREFIX + "FireEvent", EQUAL, eventtype)
endfunction

function SyncInteger takes integer playerId, integer number returns boolean
local integer x = number
local integer i = 0
local integer d = BASE
local integer n = 0
local player p
local unit u

static if (ALLOW_DEBUGGING and DEBUG_MODE) then
if (OnSelectTrigger == null) then
call BJDebugMsg(SCOPE_PREFIX + "SyncInteger: OnSelectTrigger is destroyed")
endif

if (not IsSyncEnabled()) then
call BJDebugMsg(SCOPE_PREFIX + "SyncInteger: OnSelectTrigger is disabled")
endif
endif

if (not IsSyncEnabled()) then
return false
endif

if (number < 0) then
set d = DUMMY_COUNT-1
set number = number * -1
endif

set p = Player(playerId)

loop
set x = x/(BASE)
exitwhen x==0
set i=i+1
endloop

// de-select one unit in case the players selection is full
call GroupEnumUnitsSelected(SelectionGroup, p, null)
set u = FirstOfGroup(SelectionGroup)

if (GetLocalPlayer() == p) then
call SelectUnit(u, false)
endif

loop
set n = Power[i]
set x = number/n

if (GetLocalPlayer() == p) then
call SelectUnit(SyncIntegerDummy[x], true)
call SelectUnit(SyncIntegerDummy[x], false)
endif

set number = number-x*n

exitwhen i == 0

set i = i - 1
endloop

if (GetLocalPlayer() == p) then
call SelectUnit(SyncIntegerDummy[d], true)
call SelectUnit(SyncIntegerDummy[d], false)
call SelectUnit(u, true)
endif

set u = null

return true
endfunction

//this cleans up all dummies and triggers created by the system
function SyncTerminate takes boolean destroyEvents returns nothing
local integer i = 0

if (destroyEvents) then
call DestroyTrigger(OnSelectTrigger)
call DestroyTrigger(EventTrig)
static if (LIBRARY_SyncString) then
call DestroyTrigger(SyncString_EventTrig)
endif
else
call SyncIntegerDisable()
endif

static if (LIBRARY_UnitDex) then
set UnitDex.Enabled = false
endif

loop
exitwhen i >= DUMMY_COUNT
call RemoveUnit(SyncIntegerDummy[i])
set SyncIntegerDummy[i] = null
set i = i + 1
endloop

static if (LIBRARY_UnitDex) then
set UnitDex.Enabled = true
endif
endfunction

function SyncInitialize takes nothing returns nothing
local integer i = 0

static if (ALLOW_DEBUGGING and DEBUG_MODE) then
if (OnSelectTrigger == null) then
call BJDebugMsg(SCOPE_PREFIX + "SyncInitialize: OnSelectTrigger is null and has no events attached to it")
endif
endif

static if (LIBRARY_UnitDex) then
set UnitDex.Enabled = false
endif

loop
exitwhen i >= DUMMY_COUNT
set SyncIntegerDummy[i]=CreateUnit(DUMMY_PLAYER, DUMMY_ID, 1000000, 1000000, i)

static if (ALLOW_DEBUGGING and DEBUG_MODE) then
if (i == 0) then // display once
if (SyncIntegerDummy[i] == null) then
call BJDebugMsg(SCOPE_PREFIX + "SyncInitialize: Dummy unit is null (check DUMMY_ID)")
endif

if (GetUnitAbilityLevel(SyncIntegerDummy[i], 'Aloc') > 0) then
call BJDebugMsg(SCOPE_PREFIX + "SyncInitialize: Dummy units must be selectable (detected locust)")
call UnitRemoveAbility(SyncIntegerDummy[i], 'Aloc')
endif
endif
endif

call SetUnitUserData(SyncIntegerDummy[i], i)
call PauseUnit(SyncIntegerDummy[i], true)
set i = i + 1
endloop

static if (LIBRARY_UnitDex) then
set UnitDex.Enabled = true
endif

if (GetExpiredTimer() != null) then
call DestroyTimer(GetExpiredTimer())
endif
endfunction

private function OnSelect takes nothing returns boolean
local unit u        = GetTriggerUnit()
local player p      = GetTriggerPlayer()
local integer id    = GetPlayerId(p)
local integer index = GetUnitUserData(u)
local boolean isNeg = (SyncIntegerDummy[DUMMY_COUNT-1] == u)

if (SyncIntegerDummy[index] != u) then
set u = null
return false
endif

static if (ALLOW_DEBUGGING and DEBUG_MODE) then
if (OnSelectTrigger == null) then
call BJDebugMsg(SCOPE_PREFIX + "SyncInteger: OnSelectTrigger is null")
endif
endif

if (isNeg) then
set SyncData[id] = SyncData[id]*-1
endif

if (isNeg or SyncIntegerDummy[DUMMY_COUNT-2] == u) then
set LastPlayer   = id
set LastSync     = SyncData[id]
set SyncData[id] = -1

// run "events"
set FireEvent = EVENT_SYNC_INTEGER
call TriggerEvaluate(EventTrig)
set FireEvent = 0
else
if (SyncData[id]==-1)then
set SyncData[id]=0
endif
set SyncData[id] = SyncData[id] * BASE + index
endif

set u = null

return false
endfunction

// this function is meant to be used syncstring
public function FireEvents takes real eventtype returns nothing
set FireEvent = eventtype
call TriggerEvaluate(EventTrig)
set FireEvent = 0
endfunction

//===========================================================================
private function Init takes nothing returns nothing
local integer i = 0
local integer j

loop
call TriggerRegisterPlayerUnitEvent(OnSelectTrigger, Player(i), EVENT_PLAYER_UNIT_SELECTED, null)

set SyncData[i] = -1

set i = i + 1
exitwhen i==bj_MAX_PLAYER_SLOTS
endloop

static if (AUTO_INIT) then
call TimerStart(CreateTimer(), 0, false, function SyncInitialize)
endif

static if (LIBRARY_GroupUtils) then
set SelectionGroup=ENUM_GROUP
else
set SelectionGroup=CreateGroup()
endif
set i=0
set j=1
loop
exitwhen i==32
set Power[i]=j
set j = j*BASE
set i=i+1
endloop
endfunction

static if (ALLOW_DEBUGGING and DEBUG_MODE) then
private function SyncSelectionsHook takes nothing returns nothing
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, SCOPE_PREFIX + "SyncSelectionsHook: Detected SyncSelections (can cause issues)")
endfunction

hook SyncSelections SyncSelectionsHook
endif

endlibrary

So reals are indeed the devil here.