• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] SyncInteger

Recommended: Sync Library

Demo Map:
Codeless Save and Load (Multiplayer)

Core System
JASS:
library SyncInteger uses optional UnitDex /*or any unit indexer*/, optional GroupUtils, optional xebasic, optional PlayerUtils
/***************************************************************
*
*   v1.2.1, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
*   This library allows you to send integers to all other players through
*   unit selections.
*
*   _________________________________________________________________________
*   1. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Copy the script to your map and save it (requires JassHelper *or* JNGP)
*   _________________________________________________________________________
*   2. How it works
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Creates {DUMMY_COUNT} units and assigns {BASE} of them an integer from 0-{BASE}.
*          The 2nd to last dummy is used to signal when the sequence of numbers is over and
*          the last dummy signifies a negative number.
*
*       2. Breaks down the number you want to sync to one or more {BASE} integers,
*          then selects each dummy 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} numbers. The current
*          total (starts at 0) is multiplied by {BASE} and the latest synced integer is
*          added to that. The process will repeat until it selects the 2nd to last dummy,
*          and the total is our result.
*   _________________________________________________________________________
*   3. Proper Usage
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       - Dummies must be select-able (no locust)
*
*       - Run the script in debug mode while testing
*
*   _________________________________________________________________________
*   4. Struct API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       struct SelectionSync
*         
*           static method create takes nothing returns thistype
*
*           method syncValue takes player p, integer number returns boolean
*           method destroy takes nothing returns nothing
*   _________________________________________________________________________
*   5. Function API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       function SyncInteger takes player p, integer number returns boolean
*
*       function GetSyncedInteger takes nothing returns integer
*       function GetSyncedInstance takes nothing returns integer
*       function GetSyncedPlayer takes nothing returns player
*       function GetSyncedPlayerId takes nothing returns integer
*       function IsPlayerSyncing takes player p returns boolean
*       function IsPlayerIdSyncing takes integer pid 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 filterfunc func returns triggercondition
*       function RemoveSyncEvent takes triggercondition action returns nothing
*       function TriggerRegisterSyncEvent takes trigger t, integer eventtype returns nothing
*
*       function SyncTerminate takes boolean destroyEvent returns nothing
*
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   -http://www.hiveworkshop.com/threads/syncinteger.278674/
*
*/
        globals
            // create a struct instance for global use
            public constant boolean DEFAULT_INSTANCE   = true
           
            // owner of the dummy units
            public constant player DUMMY_PLAYER        = Player(PLAYER_NEUTRAL_PASSIVE)
            // Dummy can *not* have locust and must be selectabe.
            public constant integer DUMMY_ID           = 'hfoo'
            // allow debug messages (also requries JassHelper Debug Mode)
            public constant boolean ALLOW_DEBUGGING    = true
            // higher == more dummies but less selections (faster)
            public constant integer BASE               = 10
           
            // two higher than BASE (jasshelper doesn't allow BASE + 2)
            public constant integer DUMMY_COUNT        = 12 
           
            // endconfig
            constant integer EVENT_SYNC_INTEGER = 1
           
            public integer DefaultInstance = 0
           
            private trigger OnSelectTrigger = CreateTrigger()
            private trigger EventTrig       = CreateTrigger()
            private real FireEvent          = 0
            private group SelectionGroup
            private integer LastPlayer
            private integer LastSync
            private integer LastInstance
            private player LocalPlayer
            private integer array ActiveSyncs
           
            private real DUMMY_X = 0
            private real DUMMY_Y = 0
           
            private integer array DummyInstance
        endglobals
        function GetSyncedInteger takes nothing returns integer
            return LastSync
        endfunction
        function GetSyncedPlayer takes nothing returns player
            return Player(LastPlayer)
        endfunction
       
        function GetSyncedInstance takes nothing returns integer
            return LastInstance
        endfunction
   
        function GetSyncedPlayerId takes nothing returns integer
            return LastPlayer
        endfunction
       
        function IsPlayerIdSyncing takes integer pid returns boolean
            return ActiveSyncs[pid] > 0
        endfunction
       
        function IsPlayerSyncing takes player p returns boolean
            return ActiveSyncs[GetPlayerId(p)] > 0
        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 filterfunc func returns triggercondition
            return TriggerAddCondition(EventTrig, func)
        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
   
        public function FireEvents takes real eventtype returns nothing
            set FireEvent = eventtype
            set FireEvent = 0
        endfunction
       
        // This function is called when a unit is selected.
        private function OnSelect takes nothing returns boolean
            local unit u        = GetTriggerUnit()
            local player p      = GetTriggerPlayer()
            local integer id    = GetPlayerId(p)
            local integer udata = GetUnitUserData(u)
            local SelectionSync this = DummyInstance[udata]
            local boolean isNeg = (this.Dummy[DUMMY_COUNT-1] == u)
            local integer index = this.DummyID[udata] - 1
           
            if (this <= 0) then
                return false
            endif
           
            if (isNeg) then
                set this.SyncingValue[id] = this.SyncingValue[id]*-1
            endif
            if (isNeg or this.Dummy[DUMMY_COUNT-2] == u) then
                set ActiveSyncs[id] = ActiveSyncs[id] - 1
               
                // The number is finished syncing, fire the events
                set LastPlayer   = id
                set LastSync     = this.SyncingValue[id]
                set LastInstance = this
                set FireEvent    = EVENT_SYNC_INTEGER
               
                call TriggerEvaluate(EventTrig)
               
                // Reset variables
                set FireEvent = 0
                set this.SyncingValue[id] = -1
            else
           
                if (this.SyncingValue[id] == -1) then
                    set this.SyncingValue[id] = 0
                endif
               
                // Build the number we are trying to sync
                set this.SyncingValue[id] = this.SyncingValue[id] * BASE + index
            endif
   
            set u = null
   
            return false
        endfunction
       
        private keyword SyncIntegerInit
       
        // This struct allows us to dynamically create a group of units 
        // which we can use to synchronize our integer through unit selections.
        struct SelectionSync
           
            public unit array Dummy[DUMMY_COUNT]
            public integer array DummyID[DUMMY_COUNT]
            public integer array SyncingValue[12]
           
            public static method debugger takes boolean b, string s returns nothing
                static if (ALLOW_DEBUGGING and DEBUG_MODE) then
                    if (b) then
                        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|c00FF0000" + SCOPE_PREFIX + s + "|r")
                    endif
                endif
            endmethod
       
            static method create takes nothing returns thistype
                local thistype this = thistype.allocate()
                local integer i = 0
                local integer uid
               
                debug call .debugger(OnSelectTrigger == null, "[SelectionSync.create()] OnSelectTrigger is null and has no events attached to it.")
                debug call .debugger(this.Dummy[0] != null, "[SelectionSync.create()] Dummy not null!")
               
                loop
                    exitwhen i >= DUMMY_COUNT
                   
                    set this.Dummy[i] = CreateUnit(DUMMY_PLAYER, DUMMY_ID, DUMMY_X, DUMMY_Y, i)
           
                    set uid = GetUnitUserData(this.Dummy[i])
                 
                    if (uid == 0) then
                        set uid = ( (this-1) * DUMMY_COUNT ) + (i + 1)
                        call SetUnitUserData(this.Dummy[i], uid)
                    endif
                 
                    debug call .debugger((i == 0) and (this.Dummy[i] == null), "[SelectionSync.create()] Dummy unit is null (check DUMMY_ID).")
                    debug call .debugger((i == 0) and (GetUnitAbilityLevel(this.Dummy[i], 'Aloc') > 0), "[SelectionSync.create()] Dummy units must be selectable (detected locust).")
                 
                    set this.DummyID[uid] = i + 1
                    set DummyInstance[uid] = this
                   
                    // Make dummy only selectable through triggers
                    call UnitAddAbility(this.Dummy[i], 'Amrf')
                    call SetUnitFlyHeight(this.Dummy[i], 5000, 0)
                    call UnitAddAbility(this.Dummy[i], 'Aeth')
                    call SetUnitScale(this.Dummy[i], 0, 0, 0)
                    call PauseUnit(this.Dummy[i], true)
                   
                    // Hide health bar
                    call UnitAddAbility(this.Dummy[i], 'Aloc')
                    call ShowUnit(this.Dummy[i], false)
                    call UnitRemoveAbility(this.Dummy[i], 'Aloc')
                    call ShowUnit(this.Dummy[i], true)
                   
                    set i = i + 1
                endloop
               
                return this
            endmethod
           
            method syncValue takes player p, integer number returns boolean
                local integer x = number
                local integer i = 0
                local integer d = BASE
                local integer j = 0
                local integer n = 0
                local integer l = 0
                local integer playerId = GetPlayerId(p)
                local unit u
                local unit last
                debug call .debugger(OnSelectTrigger == null, "[SelectionSync.syncValue()] OnSelectTrigger is destroyed.")
                debug call .debugger(IsSyncEnabled() == false, "[SelectionSync.syncValue()] OnSelectTrigger is disabled.")
       
                if (not IsSyncEnabled()) then
                    return false
                endif
               
                set ActiveSyncs[playerId] = ActiveSyncs[playerId] + 1
                // Check if the number is negative
                if (number < 0) then
                    set d = DUMMY_COUNT-1
                    set number = number * -1
                endif
     
                loop
                    set x = x/(BASE)
                    exitwhen x==0
                    set i=i+1
                endloop
       
                // Count how many units are selected
                call GroupEnumUnitsSelected(SelectionGroup, p, null)
                set bj_groupCountUnits = 0
                set u = FirstOfGroup(SelectionGroup)
                loop
                    exitwhen u == null
                    set last = u
                    call GroupRemoveUnit(SelectionGroup, u)
                    set bj_groupCountUnits = bj_groupCountUnits + 1
                    set u = FirstOfGroup(SelectionGroup)
                endloop
           
                // If the queue is full, de-select the last unit which
                // will allow us to select a dummy, and hopefully
                // avoid a flickering effect.
                if (bj_groupCountUnits >= 12 and LocalPlayer == p) then
                    call SelectUnit(last, false)
                endif
                set j=R2I(Pow(BASE, i))
                loop
                    set n = j
                    set x = number/n
                    set j = j / BASE
               
                    if (LocalPlayer == p) then
                        call SelectUnit(this.Dummy[x], true)
                        call SelectUnit(this.Dummy[x], false)
                    endif
           
                    set number = number-x*n
                    exitwhen i == 0
           
                    set i = i - 1
                endloop
     
                if (LocalPlayer == p) then
                    call SelectUnit(this.Dummy[d], true)
                    call SelectUnit(this.Dummy[d], false)
                 
                    if (bj_groupCountUnits >= 12) then
                        call SelectUnit(last, true)
                    endif
                endif
                set u = null
                set last = null
               
                return true
            endmethod
           
            method destroy takes nothing returns nothing
                local integer i = 0
     
                loop
                    exitwhen i >= DUMMY_COUNT
                    call RemoveUnit(this.Dummy[i])
                    set this.Dummy[i] = null
                    set i = i + 1
                endloop
            endmethod
           
            implement SyncIntegerInit
        endstruct
       
        function SyncInteger takes player p, integer number returns boolean
            debug call SelectionSync.debugger(DefaultInstance == 0, "[SyncInteger()] DefaultInstance is not initialized (make sure DEFAULT_INSTANCE is true")
           
            return SelectionSync(DefaultInstance).syncValue(p, number)
        endfunction
       
        function SyncTerminate takes boolean destroyEvents returns boolean
            local integer i = 0
           
            if (OnSelectTrigger == null and EventTrig == null) then
                return false
            endif
           
            if (destroyEvents) then
                call DestroyTrigger(OnSelectTrigger)
                call DestroyTrigger(EventTrig)
                set OnSelectTrigger = null
                set EventTrig = null
           
                static if not LIBRARY_GroupUtils then
                    call DestroyGroup(SelectionGroup)
                    set SelectionGroup = null
                endif
            else
                call SyncIntegerDisable()
            endif
           
            if (DefaultInstance > 0) then
                call SelectionSync(DefaultInstance).destroy()
            endif
           
            return true
        endfunction
        //===========================================================================
        private module SyncIntegerInit
            private static method onInit takes nothing returns nothing
                local integer i = 0
                local integer j
               
                loop
                    call TriggerRegisterPlayerUnitEvent(OnSelectTrigger, Player(i), EVENT_PLAYER_UNIT_SELECTED, null)
         
                    set i = i + 1
                    exitwhen i==bj_MAX_PLAYER_SLOTS
                endloop
                call TriggerAddCondition(OnSelectTrigger, Filter(function OnSelect))
     
                static if (LIBRARY_GroupUtils) then
                    set SelectionGroup=ENUM_GROUP
                else
                    set SelectionGroup=CreateGroup()
                endif
                static if (LIBRARY_PlayerUtils) then
                    set LocalPlayer=User.Local
                else
                    set LocalPlayer=GetLocalPlayer()
                endif
           
                set DUMMY_X = GetCameraBoundMaxX() + 2000
                set DUMMY_Y = GetCameraBoundMaxY() + 2000
               
                static if (DEFAULT_INSTANCE) then
                    set DefaultInstance = SelectionSync.create()
                endif
            endmethod
        endmodule
       
endlibrary
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
There is some minor inconvenience in this thing; since the only reason you have the optional xeBasic requirement is for the invisible dummy unit.
Why not just have an object merger call creating the required dummy and call it a day?

Besides; the way you implemented it, if the user doesn't have xeBasic, the user will get an unknown variable syntax error at the globals section.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Cool another sync system but with asynchronous execution.
I think you should point out in the documentation that it's not technically syncing a 32-bit integer. It would be great if it can sync 32 -bit integer by splitting the 32 bits into 4 bits and synching them one at a time (which was originally my plan for my sync resource but too lazy to do + I have no wc3/jngp at the moment). Then combine them at the end. And that way, the no. of dummy units is constant (16 if 4 bits)
What's different here is you're using selection events which would prevent looping through each dummy. However I thought selection events were delayed? I remember trying to make a unit unselectable using selection event and there I saw I delay (I'm not 100% sure, that was a long time ago)
 
I think you should point out in the documentation that it's not technically syncing a 32-bit integer

I don't really think it matters.

In fact I'm not even exactly sure how it works behind the scenes.


What's different here is you're using selection events which would prevent looping through each dummy. However I thought selection events were delayed? I remember trying to make a unit unselectable using selection event and there I saw I delay (I'm not 100% sure, that was a long time ago)

In my testsIsUnitSelectedwas async so it returns false for all other players immediately after callingSelectUnit

The delay in the selection event seems to be about the same as the delay in a point order. I have only tested this with 5 players, though (only 2 on b.net).

It's possible all selection events are sent/received in a group so calling SelectUnitdoesn't fire multiple requests.

I think it might be the same thing forForceUIKeyand the ESC key event, but I have more tests to do.

Why not just have an object merger call creating the required dummy and call it a day?

The editor has to be restarted with a object merger call and I want to avoid that especially since most people already have a dummy in their map.

I could just remove 'Aloc' if xebasic isn't defined.

Besides; the way you implemented it, if the user doesn't have xeBasic, the user will get an unknown variable syntax error at the globals section.

true next version will avoid that
 
Updated. I will be adding a timeout feature but that will require preloading timers or data attaching (timerutils).

Code:
v1.0.1

[SyncString]
- SyncString no longer needs to populate every string to the maximum length. This is a huge performance increase for users who know how long their string is going to be.
- Other minor improvements

[SyncInteger]
- SyncInitialize function added to allow users to (re)initialize the system whenever they want
- SyncTerminate now takes paramter "destroyEvents" which will destroy the triggers instead of disabling them (which makes the system un-usable from then on)
- SyncTerminate now removes handles from the SyncString library if it's present
- Added AUTO_INIT constant
- XE_DUMMY_UNITID no longer hardcoded into config
- Many improvements to debugging
- SyncSelections is now detected in debug mode and the system will warn you *not* to use it (very useful to GUI users since a couple functions that deal with selected units use that)
- Improved variable names
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Ok, since nobody has understood or read what i've written about that, a code should be better :

JASS:
library Sync initializer Init

    globals
        timer Tim = CreateTimer()
        real T1
        real T2
        private unit End_sync
        private group Units = CreateGroup()
        private integer array Last_sync
        private unit array U
    endglobals
    
    function SyncInteger takes integer sync returns nothing
        local integer x = sync
        local integer i=0
        set Last_sync[0]=0
        set Last_sync[1]=0
        set T1 = TimerGetElapsed(Tim)
        loop
            set x = x/10
            exitwhen x==0
            set i=i+1
        endloop
        loop
            set x = R2I(sync/Pow(10,i))
            call SelectUnit(U[x],true)
            call SelectUnit(U[x],false)
            //call BJDebugMsg(I2S(x))
            set sync = R2I(sync-x*Pow(10,i))
            exitwhen i==0
            set i=i-1
        endloop
        call SelectUnit(End_sync,true)
        call SelectUnit(End_sync,false)
    endfunction
    
    private function OnSelect takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer id = GetPlayerId(GetTriggerPlayer())
        if not IsUnitInGroup(u,Units) then
            return false
        endif
        if u == End_sync then
            set T2 = TimerGetElapsed(Tim)
            call BJDebugMsg("end of sync : "+R2S(T2-T1) + " Player(" + I2S(id) + ") == " + I2S(Last_sync[id]))
            return false
        endif
        set Last_sync[id]=Last_sync[id]*10+GetUnitUserData(u)
        return false
    endfunction
    
    private function OnDeselect takes nothing returns boolean
        return false
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger trig = CreateTrigger()
        local integer i = -1
        local integer data
        call TriggerRegisterPlayerSelectionEventBJ(trig,Player(0),true)
        call TriggerRegisterPlayerSelectionEventBJ(trig,Player(1),true)
        call TriggerAddCondition(trig,function OnSelect)
        set trig = CreateTrigger()
        call TriggerRegisterPlayerSelectionEventBJ(trig,Player(0),false)
        call TriggerRegisterPlayerSelectionEventBJ(trig,Player(1),false)
        call TriggerAddCondition(trig,function OnDeselect)
        call TimerStart(Tim,3600,false,null)
        loop
        exitwhen i == 10
        set i=i+1
            set U[i] = CreateUnit(Player(3),'hfoo',0,0,0)
            call SetUnitUserData(U[i],i)
            call GroupAddUnit(Units,U[i])
        endloop
        set End_sync = CreateUnit(Player(3),'hfoo',0,0,0)
        call GroupAddUnit(Units,End_sync)
        
    endfunction
    
endlibrary
.

JASS:
scope Test initializer Init

    private function Cond takes nothing returns boolean
        local integer i = S2I(GetEventPlayerChatString())
        call BJDebugMsg(R2S(TimerGetElapsed(Tim)))
        if GetLocalPlayer() == Player(1) then
            set i = 9
        endif
        call SyncInteger(i)
        return false
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger trig = CreateTrigger()
        call TriggerRegisterPlayerChatEvent(trig,Player(0),"",false)
        call TriggerAddCondition(trig,Filter(function Cond))
    endfunction
endscope
Ofc there it's only a proof of concept, it doesn't handle negative numbers, and so one.
But i don't know if it's really reliable, seems so, if you don't have to do more than 9 selections, included the end_sync unit, and then the result start to glitch.
I didn't investigate more about that but i was not because of overflow (well, not really tested this option in fact), maybe a bug in my code or a selection hardcoded limitation.
 
Your implementation is probably better since it doesn't need to preload as many dummies. I have already implemented it in the next version :thumbs_up:

Also,SelectUnit(u,true)would fail if the player's selection was full, correct? If so I was considering adding a constant to allow betweenClearSelectionand simply selecting/unselecting the unit, but then people's implementations would be different (some handling reselection, some not).

EDIT: Figure I will just post the current version.

Updated, 1.0.2. need to update documentation / changelog
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
I think you can only select 12 units at a time. afaik

With the "pick every unit action" or whatever it spells, i suppose, but i don't think there is a limit with the single action SelectUnit.
But meh, these kind of tests are up to the resource maker :thumbs_up:

Seriously ClearSelection makes it very lame imho, especially because i suppose it is noticeable in game.
 
Seriously ClearSelection makes it very lame imho, especially because i suppose it is noticeable in game.

It's not noticeable if you handle the re-selection, but yes it sucks I'll look into it more.

EDIT:

Updated, along with documentation.

Code:
1.0.3

[SyncInteger]
- Replaced ClearSelection with SelectUnit(u, false)
- SyncInteger handles re-selection on it's own now

1.0.2

[SyncInteger]
- Dummy count reduced
- System now breaks down a number and sends it in pieces
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
I still highly doubt there is a practical limit number selection.
Even if there is, i don't think that the actual code will handle that.

My theory is that unit selections/deselections are delayed because they are handled each X seconds (or something like that). Ofc i can be wrong that's just a theory without any tests.
So, when you will order several selections at a time and the player selection is full-1, then you will still reach the limit.

You confirm that this random selection/deselection of unit is not noticeable ?
For example if you spam orders with actual selected units.

Why not just use FirstOfGroup instead of random unit ?
It's no like you use every index of your unit array variable anyway.

Also, it seems that if you select/deselect several times the same unit without any delay, like example you try to sync the value 1111, it takes less time than let's say 1234.
I say that because for negative numbers you could use also SyncIntegerDummy[10]

Very important, as said with my code the result was not correct if i tried to sync a number with more than 8 digits, like let's say 123456789, have you tested it ?
 
I still highly doubt there is a practical limit number selection.
Even if there is, i don't think that the actual code will handle that.

What do you mean? If you try to select a unit when the queue is full it fails.

My theory is that unit selections/deselections are delayed because they are handled each X seconds (or something like that). Ofc i can be wrong that's just a theory without any tests.

The delay is fairly small and I've explained it more in my other thread. I've sent 32 integers in under a second with a 4 player map.

But yes it's possible it's sent every X seconds with each selection in a group/list.

You confirm that this random selection/deselection of unit is not noticeable ?
For example if you spam orders with actual selected units.

The selections are done locally so yes there's nothing noticeable. Unless I'm missing something?

Why not just use FirstOfGroup instead of random unit ?
It's no like you use every index of your unit array variable anyway.

Dumb mistake, I was updating this kind of late last night.

I say that because for negative numbers you could use also SyncIntegerDummy[10]

Yeah I could use another dummy to signal a negative number.

Very important, as said with my code the result was not correct if i tried to sync a number with more than 8 digits, like let's say 123456789, have you tested it ?

I guess I missed that. No I haven't tested it but regardless this implementation is better.

If it fails at 8 digits and I can't fix it then I'll create a disclaimer and a debug message.

Can always send huge numbers via a higher base string.

EDIT: Some 8 digit numbers worked but higher ones didn't. Is that due to some inaccuracy in converting the base?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
What do you mean? If you try to select a unit when the queue is full it fails.

Let's say the limit is 32, the player has actually 32 unit selected.
You remove one selection -> 31
But you try to select 8 units -> 39

The selections are done locally so yes there's nothing noticeable. Unless I'm missing something?
Imagine you click as a pro gamer, many many mouse clicks and eventually keyboard in one second for a group of units selected.
What i mean is that : the unit you select/deselect is always selected from the point of view of player ?
All orders will be received ? None will fail because the unit was not selected during a short interval of time ?

Yeah I could use another dummy to signal a negative number.
I mean for negative numbers you could use this very same unit at the begin of the sync.
Because you will use it also at the end of sync, and that should mean the total sync should be faster than if you used a dedicaced unit for that (if what i've said is correct)

I guess I missed that. No I haven't tested it but regardless this implementation is better.

If it fails at 8 digits and I can't fix it then I'll create a disclaimer and a debug message.
Or just increase the base (more than hexadecimal) until you don't need more than 8 digit to cover all integers (if it doesn't need that much dummies, i have not calculated it)
Or maybe better add constants to let the user choice ?

EDIT : Maybe, as said i have not investigated about the bug, gogo debug text messages :thumbs_up:
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
32bit int can actually store 10 numbers.

But then you would need to select certain units up to 9 times, if the number is like 999999 you need literally 6*9 selections, if you want 10 dummy units.

Hmm no, in the worst case you will need 10 selections for each digit, one for negative and one for end of sync.
So 12 for the lowest negative integer number : -2147483648

If there is this 9 selection limitation, then we need to solve that :

x⁽⁷⁾ -1 >= 2147483648.

So we need a 22 base at least.
 
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:
1.0.5

[SyncInteger]
- Added RemoveSyncEvent
- OnSyncInteger returns trigger condition (for use with RemoveSyncEvent)
- Added IsPlayerIdSyncing
- Other minor changes
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
@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:
Level 31
Joined
Jul 10, 2007
Messages
6,306
@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:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Ah, I see now.

Thanks Troll-Brain.

I would use base 64 as bits can be packed into that.

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.
 
I don't think TH has even tested on battle.net

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.

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

Yes it is.

-------

I will be updating this soon enough
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
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.
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.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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:
Here is some basic test code for GC in case some are wondering.

JASS:
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

JASS:
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:
I know the truth.

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

haha except the new library requires the old one

I still need to fix it

EDIT:

Current implementation with GC. I will hopefully be adding .addStr and .addUnit as well.

JASS:
//callback handled internally for now

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

loop
	exitwhen i > 1000
	call d.addInt(i)
	set i = i + 1
endloop

call d.start()
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Seems to work like a charm :

JASS:
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
            return TriggerAddCondition(EventTrig, Filter(func))
        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 UnitAddAbility(SyncIntegerDummy[i], DUMMY_ABILITY)
                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

            call TriggerAddCondition(OnSelectTrigger, Filter(function OnSelect))
           
            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.
 
Update - v1.0.9
  • The system now only de-selects a unit if the player's unit selection is full. This fixes a bug (in most cases) where it would cancel the mouse when trying to order a unit.
  • In SyncTerminate, SelectionGroup is now destroyed when GroupUtils is not found, and references to SyncString removed.
  • Incremented the dummy assigned value by 1 to avoid a value of 0.
  • Local player cached.
  • Added optional xebasic and optional PlayerUtils requirements.
 
Update - v1.1.0
  • SyncInteger & SyncNotify now take player instead of integer for player id.
  • Reworked notification system. The dummy unit is now created internally and you are given a notification id.
  • Can now use any unit indexer (or still none).
  • Cleaned up debugging messages.
  • Dummy units are created specifically at map boundaries instead of at some random large coordinates.
 
Updated: v1.2.0

  • struct SelectionSync implemented which allows the user to create new instances of the system. Previously, you could only work with a single instance which was prone to conflicts if multiple scripts used this library.
  • Added function GetSyncedInstance which returns an instance of SelectionSync that is associated with the synced data.
  • function GetSyncedPlayerId implemented which allows you to get the syncing player by their index.
  • Configuration boolean DEFAULT_INSTANCE added, which when true will create a default instance of the system for use with the SyncInteger function (provides backwards compatibility).
  • Removed the functionality for notifications which allowed you to signal when a process was complete with a single selection.
  • The system is now initialized from a module.
 
Last edited:
Top