1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] Sync (Game Cache)

Discussion in 'JASS Resources' started by TriggerHappy, May 14, 2016.

  1. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,672
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Documentation

    Uses:
    1. SyncInteger (Required)
    2. PlayerUtils (Optional)

    Demo Map: Codeless Save and Load (Multiplayer)

    Core System
    Code (vJASS):
    library Sync requires SyncInteger, optional PlayerUtils
    /***************************************************************
    *
    *   v1.3.0, by TriggerHappy
    *   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    *
    *   This library allows you to quickly synchronize async data such as
    *   the contents of a local file to all players in the map by using the game cache.
    *
    *   Full Documentation: -http://www.hiveworkshop.com/forums/pastebin.php?id=p4f84s
    *
    *   _________________________________________________________________________
    *   1. Installation
    *   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    *   Copy the script to your map and save it (requires JassHelper *or* JNGP)
    *
    *       SyncInteger: https://www.hiveworkshop.com/threads/syncinteger.278674/
    *       PlayerUtils: https://www.hiveworkshop.com/threads/playerutils.278559/
    *   _________________________________________________________________________
    *   2. API
    *   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    *       struct SyncData
    *
    *           method start takes nothing returns nothing
    *           method startChunk takes integer i, integer end returns nothing
    *           method refresh takes nothing returns nothing
    *           method destroy takes nothing returns nothing
    *
    *           method addInt takes integer i returns nothing
    *           method addReal takes integer i returns nothing
    *           method addString takes string s, integer len returns nothing
    *           method addBool takes booleanflag returns nothing
    *
    *           method readInt takes integer index returns integer
    *           method readReal takes integer index returns integer
    *           method readString takes integer index returns string
    *           method readBool takes integer index returns boolean
    *
    *           method hasInt takes integer index returns boolean
    *           method hasReal takes integer index returns boolean
    *           method hasString takes integer index returns boolean
    *           method hasBool takes integer index returns boolean
    *
    *           method isPlayerDone takes player p returns boolean
    *           method isPlayerIdDone takes integer pid returns boolean
    *
    *           method addEventListener takes filterfunc func returns nothing
    *
    *           ---------
    *
    *           filterfunc onComplete
    *           filterfunc onError
    *           filterfunc onUpdate
    *           trigger trigger
    *
    *           readonly player from
    *
    *           readonly real timeStarted
    *           readonly real timeFinished
    *           readonly real timeElapsed
    *
    *           readonly integer intCount
    *           readonly integer boolCount
    *           readonly integer strCount
    *           readonly integer realCount
    *           readonly integer playersDone
    *
    *           readonly boolean buffering
    *
    *           readonly static integer last
    *           readonly static player LocalPlayer
    *           readonly static boolean Initialized
    *
    *           static method create takes player from returns SyncData
    *           static method destroy takes nothing returns nothing
    *           static method gameTime takes nothing returns real
    *
    *       function GetSyncedData takes nothing returns SyncData
    *
    ***************************************************************/


        globals
            // characters that can be synced (ascii)
            private constant string ALPHABET                    = " !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"\t\n"

            // safe characters for use in game cache keys
            // (case sensitive)
            private constant string SAFE_KEYS                   = " !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`{|}~\"\t\n"

            // stop reading the string buffer when reaching this char (this char cannot be synced)
            private constant string TERM_CHAR                   = "~"
           
            // how fast the buffer updates
            private constant real UPDATE_PERIOD                 = 0.03125

            // automatically recycle indices when the syncing player leaves
            private constant boolean AUTO_DESTROY_ON_LEAVE      = true

            // automatically stop buffering when an error occurs
            private constant boolean STOP_BUFFERING_ON_ERROR    = true

            // preload game cache key strings on init
            private constant boolean PRELOAD_STR_CACHE          = true

            // size of the alphabet
            private constant integer ALPHABET_BASE              = StringLength(ALPHABET)

            // maximum number of strings *per instance*
            private constant integer MAX_STRINGS                = 8192 // or any arbitrary high number

            // filenames for gc (short names have faster sync time)
            private constant string CACHE_FILE                  = "i"
            private constant string CACHE_FILE_STR              = "s"

            // don't edit below this line
            constant integer EVENT_SYNC_CACHE       = 3
           
            constant integer SYNC_ERROR_TIMEOUT     = 1
            constant integer SYNC_ERROR_PLAYERLEFT  = 2
           
            private SelectionSync Synchronizer
        endglobals

        //**************************************************************

        function GetSyncedData takes nothing returns SyncData
            return SyncData(SyncData.Last)
        endfunction

        public function I2Char takes string alphabet, integer i returns string
            return SubString(alphabet, i, i + 1)
        endfunction

        public function Char2I takes string alphabet, string c returns integer
            local integer i = 0
            local string s
            local integer l = StringLength(alphabet)
            loop
                set s = I2Char(alphabet, i)
                exitwhen i == l
                if (s == c) then
                    return i
                endif
                set i = i + 1
            endloop
            return 0
        endfunction

        public function ConvertBase takes string alphabet, integer i returns string
            local integer b
            local string s = ""
            local integer l = StringLength(alphabet)
            if i < l then
                return I2Char(alphabet, i)
            endif
            loop
                exitwhen i <= 0
                set b = i - ( i / l ) * l
                set s = I2Char(alphabet, b) + s
                set i = i / l
            endloop
            return s
        endfunction

        public function PopulateString takes string s, integer makeLen returns string
            local integer i = 0
            local integer l = StringLength(s)
            if (l == makeLen) then
                return s
            endif
            set l = makeLen-l
            loop
                exitwhen i > l
                set s = s + TERM_CHAR
                set i = i + 1
            endloop
            return s
        endfunction

        //**************************************************************

        globals
            // string table keys
            private constant integer KEY_STR_POS = (0*MAX_STRINGS)
            private constant integer KEY_STR_LEN = (1*MAX_STRINGS)

            // pending data storage space
            private constant integer KEY_STR_CACHE = (2*MAX_STRINGS)
        endglobals

        struct SyncData

            real timeout
            filterfunc onComplete
            filterfunc onError
            filterfunc onUpdate
            trigger trigger

            readonly integer lastError

            readonly player from

            readonly real timeStarted
            readonly real timeFinished
            readonly real timeElapsed

            readonly integer intCount
            readonly integer boolCount
            readonly integer strCount
            readonly integer realCount
            readonly integer playersDone

            readonly boolean buffering

            readonly static boolean Initialized = false
            readonly static integer Last        = 0
            readonly static player LocalPlayer
            readonly static integer LocalPlayerID

            private static integer Running   = 0
            private static real timeCounter  = 0.00
            private static trigger EventTrig = CreateTrigger()

            private static hashtable Table
            private static hashtable CharTable
            private static gamecache array Cache
            private static integer array PendingCount
            private static timer Elapsed
            private static timer BufferTimer
            private static integer AlphaHash
         
            private integer strBufferLen
            private trigger eventTrig
            private string mkey
            private boolean localFinished

            private thistype next
            private thistype prev
         
            static method bool2I takes boolean b returns integer
                if (b) then
                    return 1
                endif
                return 0
            endmethod
         
            private static method hashString takes string c returns integer
                return StringHash(I2S(bool2I(StringCase(c, true) == c)) + c)
            endmethod

            static method char2I takes string alphabet, string c returns integer // requires preloading table with data
                return LoadInteger(SyncData.CharTable, .AlphaHash, .hashString(c))
            endmethod

            private method resetVars takes nothing returns nothing
                set this.intCount       = 0
                set this.strCount       = 0
                set this.boolCount      = 0
                set this.realCount      = 0
                set this.playersDone    = 0
                set this.strBufferLen   = 0
                set this.timeStarted    = 0
                set this.timeFinished   = 0
                set this.lastError      = 0
                set this.onComplete     = null
                set this.onError        = null
                set this.onUpdate       = null
                set this.timeout        = 0.00
                set this.buffering      = false
                set this.localFinished  = false
                set this.trigger        = null
            endmethod

            private static method getKey takes integer pos returns string
                local string position=""
       
                if (HaveSavedString(Table, KEY_STR_CACHE, pos)) then
                    return LoadStr(Table, KEY_STR_CACHE, pos)
                endif
       
                set position = ConvertBase(SAFE_KEYS, pos)
                call SaveStr(Table, KEY_STR_CACHE, pos, position)
       
                return position
            endmethod

            static method create takes player from returns thistype
                local thistype this

                // Player has to be playing because of GetLocalPlayer use.
                if (GetPlayerController(from) != MAP_CONTROL_USER or GetPlayerSlotState(from) != PLAYER_SLOT_STATE_PLAYING) then
                    return 0
                endif

                set this = thistype.allocate()

                set this.from   = from
                set this.mkey   = getKey(this-1)

                call this.resetVars()

                set thistype(0).next.prev = this
                set this.next = thistype(0).next
                set thistype(0).next = this

                set this.prev = 0

                return this
            endmethod

            method refresh takes nothing returns nothing
                local integer i = 0
                local integer p = 0
           
                loop
                    static if (LIBRARY_PlayerUtils) then
                        exitwhen i == User.AmountPlaying
                        set p = User.fromPlaying(i).id
                    else
                        exitwhen i == bj_MAX_PLAYER_SLOTS
                        set p = i
                    endif

                    call RemoveSavedInteger(Table, this, KEY_STR_POS + p)
                    call RemoveSavedInteger(Table, this, KEY_STR_LEN + p)
                    call RemoveSavedBoolean(Table, p, this) // playerdone

                    set i = i + 1
                endloop

                call FlushStoredMission(Cache[0], this.mkey)
                call FlushStoredMission(Cache[1], this.mkey)

                call this.resetVars()
            endmethod

            method destroy takes nothing returns nothing
                if (this.eventTrig != null) then
                    call DestroyTrigger(this.eventTrig)
                    set this.eventTrig=null
                endif

                call this.refresh()

                set this.next.prev = this.prev
                set this.prev.next = this.next

                call this.deallocate()
            endmethod

            method hasInt takes integer index returns boolean
                return HaveStoredInteger(Cache[0], this.mkey, getKey(index))
            endmethod

            method hasReal takes integer index returns boolean
                return HaveStoredReal(Cache[0], this.mkey, getKey(index))
            endmethod

            method hasBool takes integer index returns boolean
                return HaveStoredBoolean(Cache[0], this.mkey, getKey(index))
            endmethod

            method hasString takes integer index returns boolean
                local integer i = LoadInteger(Table, this, KEY_STR_POS+index)
                if (index > 0 and i == 0) then
                    return false
                endif
                return HaveStoredInteger(Cache[1], this.mkey, getKey(i + LoadInteger(Table, this, KEY_STR_LEN+index)))
            endmethod

            method addInt takes integer i returns nothing
                local string position=getKey(intCount)
       
                if (LocalPlayer == this.from) then
                    call StoreInteger(Cache[0], this.mkey, position, i)
                endif
       
                set intCount=intCount+1
            endmethod

            method addReal takes real i returns nothing
                local string position=getKey(realCount)
       
                if (LocalPlayer == this.from) then
                    call StoreReal(Cache[0], this.mkey, position, i)
                endif
       
                set realCount=realCount+1
            endmethod

            method addBool takes boolean flag returns nothing
                local string position=getKey(boolCount)
       
                if (LocalPlayer == this.from) then
                    call StoreBoolean(Cache[0], this.mkey, position, flag)
                endif
       
                set boolCount=boolCount+1
            endmethod

            // SyncStoredString doesn't work
            method addStringEx takes string s, integer maxLen, boolean doSync returns nothing
                local string position
                local integer i = 0
                local integer strPos = 0
                local integer strLen = 0

                if (StringLength(s) < maxLen) then
                    set s = PopulateString(s, maxLen)
                endif

                // store the string position in the table
                if (strCount == 0) then
                    call SaveInteger(Table, this, KEY_STR_POS, 0)
                else
                    set strLen = LoadInteger(Table, this, KEY_STR_LEN + (strCount-1)) + 1
                    set strPos = LoadInteger(Table, this, KEY_STR_POS + (strCount-1)) + strLen

                    call SaveInteger(Table, this, KEY_STR_POS + strCount, strPos)
                endif

                // convert each character in the string to an integer
                loop
                    exitwhen i > maxLen

                    set position = getKey(strPos + i)

                    if (LocalPlayer == this.from) then
                        call StoreInteger(Cache[1], this.mkey, position, .char2I(ALPHABET, SubString(s, i, i + 1)))
                        if (doSync and LocalPlayer == this.from) then
                            call SyncStoredInteger(Cache[1], this.mkey, position)
                        endif
                    endif

                    set i = i + 1
                endloop

                set strBufferLen = strBufferLen + maxLen
                call SaveInteger(Table, this, KEY_STR_LEN+strCount, maxLen) // store the length as well
                set strCount=strCount+1
            endmethod
           
            method addString takes string s, integer maxLen returns nothing
                call addStringEx(s, maxLen, false)
            endmethod

            method readInt takes integer index returns integer
                return GetStoredInteger(Cache[0], this.mkey, getKey(index))
            endmethod

            method readReal takes integer index returns real
                return GetStoredReal(Cache[0], this.mkey, getKey(index))
            endmethod

            method readBool takes integer index returns boolean
                return GetStoredBoolean(Cache[0], this.mkey, getKey(index))
            endmethod

            method readString takes integer index returns string
                local string s = ""
                local string c
                local integer i = 0
                local integer strLen = LoadInteger(Table, this, KEY_STR_LEN+index)
                local integer strPos
       
                if (not hasString(index)) then
                    return null
                endif

                set strLen = LoadInteger(Table, this, KEY_STR_LEN+index)
                set strPos = LoadInteger(Table, this, KEY_STR_POS+index)
       
                loop
                    exitwhen i > strLen
           
                    set c = I2Char(ALPHABET, GetStoredInteger(Cache[1], this.mkey, getKey(strPos + i)))

                    if (c == TERM_CHAR) then
                        return s
                    endif

                    set s = s + c
                    set i = i + 1
                endloop

                return s
            endmethod

            private method fireListeners takes nothing returns nothing
                set Last = this

                if (this.eventTrig != null) then
                    call TriggerEvaluate(this.eventTrig)
                endif

                if (this.trigger != null and TriggerEvaluate(this.trigger)) then
                    call TriggerExecute(this.trigger)
                endif
            endmethod

            private method fireEvent takes filterfunc func returns nothing
                set Last = this

                call TriggerAddCondition(EventTrig, func)
                call TriggerEvaluate(EventTrig)
                call TriggerClearConditions(EventTrig)
            endmethod

            method addEventListener takes filterfunc func returns nothing
                if (this.eventTrig == null) then
                    set this.eventTrig = CreateTrigger()
                endif
                call TriggerAddCondition(this.eventTrig, func)
            endmethod

            public static method gameTime takes nothing returns real
                return timeCounter + TimerGetElapsed(Elapsed)
            endmethod

            private method error takes integer errorId returns nothing
                set this.lastError = errorId

                if (this.onError != null) then
                    call this.fireEvent(this.onError)
                endif

                call this.fireListeners()

                static if (STOP_BUFFERING_ON_ERROR) then
                    set this.buffering = false
                endif
            endmethod

            private static method readBuffer takes nothing returns nothing
                local boolean b = true
                local integer i = 0
                local thistype data = thistype(0).next

                loop
                    exitwhen data == 0

                    // find the nearest instance that is still buffering
                    loop
                        exitwhen data.buffering or data == 0
                        set data=data.next
                    endloop

                    // if none are found, exit
                    if (not data.buffering) then
                        return
                    endif
                   
                    set data.timeElapsed = data.timeElapsed + UPDATE_PERIOD

                    if (data.onUpdate != null) then
                        call data.fireEvent(data.onUpdate)
                    endif

                    if (data.timeout > 0 and data.timeElapsed > data.timeout) then
                        call data.error(SYNC_ERROR_TIMEOUT)
                    endif

                    // if the player has left, destroy the instance
                    if (GetPlayerSlotState(data.from) != PLAYER_SLOT_STATE_PLAYING) then
                        call data.error(SYNC_ERROR_PLAYERLEFT)
                        static if (AUTO_DESTROY_ON_LEAVE) then
                            call data.destroy()
                        endif
                    endif

                    set b = true

                    // make sure all integers have been synced
                    if (data.intCount > 0 and  not data.hasInt(data.intCount-1)) then
                        set b = false
                    endif

                    // make sure all reals have been synced
                    if (data.realCount > 0 and not data.hasReal(data.realCount-1)) then
                        set b = false
                    endif

                    // check strings too
                    if (data.strCount > 0 and not data.hasString(data.strCount-1)) then
                        set b = false
                    endif

                    // and booleans
                    if (data.boolCount > 0 and not data.hasBool(data.boolCount-1)) then
                        set b = false
                    endif

                    // if everything has been synced
                    if (b) then

                        if (not data.localFinished) then // async
                            set data.localFinished = true

                            // notify everyone that the local player has recieved all of the data
                            call Synchronizer.syncValue(LocalPlayer, data)
                        endif
                   
                    endif

                    set data = data.next
                endloop
            endmethod
           
            public method initInstance takes nothing returns nothing
                if (this.timeStarted != 0.00) then
                    return
                endif

                set this.timeStarted = gameTime()
                set this.playersDone = 0
                set this.buffering   = true
                set this.timeElapsed = (UPDATE_PERIOD - TimerGetElapsed(BufferTimer)) * -1
       
                if (Running==0) then
                    call TimerStart(BufferTimer, UPDATE_PERIOD, true, function thistype.readBuffer)
                    call thistype.readBuffer()
                endif

                set Running=Running+1
            endmethod
           
            method syncInt takes integer i returns nothing
                local string position = getKey(intCount)
                call this.addInt(i)
                if (LocalPlayer == this.from) then
                    call SyncStoredInteger(Cache[0], this.mkey, position)
                endif
                call this.initInstance()
            endmethod
           
            method syncReal takes real r returns nothing
                local string position = getKey(realCount)
                call this.addReal(r)
                if (LocalPlayer == this.from) then
                    call SyncStoredReal(Cache[0], this.mkey, position)
                endif
                call this.initInstance()
            endmethod
           
            method syncBoolean takes boolean b returns nothing
                local string position = getKey(boolCount)
                call this.addBool(b)
                if (LocalPlayer == this.from) then
                    call SyncStoredReal(Cache[0], this.mkey, position)
                endif
                call this.initInstance()
            endmethod
           
            method syncString takes string s, integer maxLen returns nothing
                local string position = getKey(strCount)
                call this.addStringEx(s, maxLen, true)
                call this.initInstance()
            endmethod

            method startChunk takes integer i, integer end returns boolean
                local integer n = 0
                local integer j = 0
                local integer p = 0
                local string position

                if (this.timeStarted != 0.00) then
                    return false
                endif
               
                // Begin syncing
                loop
                    exitwhen i > end

                    set position = LoadStr(Table, KEY_STR_CACHE, i)
         
                    if (i < intCount and LocalPlayer == this.from) then
                        call SyncStoredInteger(Cache[0], this.mkey, position)
                    endif
                    if (i < realCount and LocalPlayer == this.from) then
                        call SyncStoredReal(Cache[0], this.mkey, position)
                    endif
                    if (i < boolCount and LocalPlayer == this.from) then
                        call SyncStoredBoolean(Cache[0], this.mkey, position)
                    endif
         
                    if (i < strCount and LocalPlayer == this.from) then
                        set n = LoadInteger(Table, this, KEY_STR_LEN + i)
                        set p = LoadInteger(Table, this, KEY_STR_POS + i)
             
                        set j = 0
             
                        loop
                            exitwhen j > n
                 
                            set position = LoadStr(Table, KEY_STR_CACHE, p + j)

                            if (LocalPlayer == this.from) then
                                call SyncStoredInteger(Cache[1], this.mkey, position)
                            endif

                            set j = j + 1
                        endloop
                    endif
         
                    set i = i + 1
                endloop
       
                call this.initInstance()
               
                return true
            endmethod

            method start takes nothing returns boolean
                local integer l = intCount

                // Find the highest count
                if (l < realCount) then
                    set l = realCount
                endif
                if (l < strCount) then
                    set l = strCount
                endif
                if (l < boolCount) then
                    set l = boolCount
                endif

                return startChunk(0, l)
            endmethod

            method isPlayerIdDone takes integer pid returns boolean
                return LoadBoolean(Table, pid, this)
            endmethod

            method isPlayerDone takes player p returns boolean
                return isPlayerIdDone(GetPlayerId(p))
            endmethod

            private static method updateStatus takes nothing returns boolean
                local integer i = 0
                local integer p = GetSyncedPlayerId()
                local boolean b = true
                local boolean c = true
                local thistype data = GetSyncedInteger()
                local triggercondition tc
               
                if (GetSyncedInstance() != Synchronizer or not data.buffering) then
                    return false
                endif
         
                set data.playersDone = data.playersDone + 1
                call SaveBoolean(Table, p, data, true) // set playerdone

                // check if everyone has received the data
                loop
                    static if (LIBRARY_PlayerUtils) then
                        exitwhen i == User.AmountPlaying
                        set p = User.fromPlaying(i).id
                        set c = User.fromPlaying(i).isPlaying
                    else
                        exitwhen i == bj_MAX_PLAYER_SLOTS
                        set p = i
                        set c = (GetPlayerController(Player(p)) == MAP_CONTROL_USER and GetPlayerSlotState(Player(p)) == PLAYER_SLOT_STATE_PLAYING)
                    endif
           
                    if (c and not data.isPlayerIdDone(p)) then
                        set b = false // someone hasn't
                    endif

                    set i = i + 1
                endloop

                // if everyone has recieved the data
                if (b) then
                    set Running = Running-1

                    if (Running == 0) then
                        call PauseTimer(BufferTimer)
                    endif
         
                    set data.buffering    = false
                    set data.timeFinished = gameTime()
                    set data.timeElapsed  = data.timeFinished - data.timeStarted
             
                    // fire events
                    if (data.onComplete != null) then
                        call data.fireEvent(data.onComplete)
                    endif

                    call data.fireListeners()
                    call SyncInteger_FireEvents(EVENT_SYNC_CACHE)
                endif

                return false
            endmethod

            private static method trackTime takes nothing returns nothing
                set timeCounter = timeCounter + 10
            endmethod

            private static method preloadChar2I takes nothing returns nothing
                local integer i = 0
                local string c
             
                set .AlphaHash = .hashString(ALPHABET)
             
                loop
                    exitwhen i >= ALPHABET_BASE

                    set c = I2Char(ALPHABET, i)

                    call SaveInteger(SyncData.CharTable, .AlphaHash, .hashString(c), Char2I(ALPHABET, c))

                    set i = i + 1
                endloop
            endmethod

            private static method onInit takes nothing returns nothing
                static if (SyncInteger_DEFAULT_INSTANCE) then
                    set Synchronizer = SyncInteger_DefaultInstance
                else
                    set Synchronizer = SelectionSync.create()
                endif
               
                set Table = InitHashtable()
                set CharTable = InitHashtable()
             
                set Cache[0] = InitGameCache(CACHE_FILE)
                set Cache[1] = InitGameCache(CACHE_FILE_STR)

                set Elapsed     = CreateTimer()
                set BufferTimer = CreateTimer()

                static if (LIBRARY_PlayerUtils) then
                    set LocalPlayer   = User.Local
                    set LocalPlayerID = User.fromLocal().id
                else
                    set LocalPlayer   = GetLocalPlayer()
                    set LocalPlayerID = GetPlayerId(LocalPlayer)
                endif

                call OnSyncInteger(Filter(function thistype.updateStatus))
                call TimerStart(Elapsed, 10., true, function thistype.trackTime)
       
                static if (PRELOAD_STR_CACHE) then
                    loop
                        exitwhen Last == ALPHABET_BASE
                        call getKey(Last)
                        set Last = Last + 1
                    endloop
                    set Last = 0
                endif
       
                call preloadChar2I()

                set Initialized = true
            endmethod

        endstruct

    endlibrary
     

    Attached Files:

    Last edited: Aug 8, 2018
  2. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,672
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    LAN test results: here's 7 player, 2 PCs

    I'm wondering if I should implement a
    hashtable
    for unlimited instances.

    I don't think people ill be needing many instances of this though.

    Also, currently the system loops through the entire game cache and makes sure all values are there. I wonder if it's safe to just check the first/last index in the cache.

    It's been safe in my tests but I don't know if it's 100%.


    EDIT:

    Updated.

    I might remove units unless I can find a way to create them locally, or to use it some other way.

    Code (Text):

    v1.0.1

    - Added support for units and booleans
     
    EDIT #2:

    Updated.

    Code (Text):

    v1.0.2

    - Added a readonly boolean Initialized to the struct
    - Added hasInt, hasString, hasBool
    - Elapsed timer now uses a pre-loaded one
    - Now only checks the first and last index of the cache
    - Shortened key and mission key values to increase performance in some cases
    - Fixed a bug that would occur due to cache keys being case in-sensitive
    - Improved variable names
    - Removed unit support
     
     
    Last edited: May 14, 2016
  3. neo_sluf

    neo_sluf

    Joined:
    Feb 5, 2012
    Messages:
    1,432
    Resources:
    5
    Maps:
    5
    Resources:
    5
    Can I ask when is this used? Sorry I have no idea in Async Data.
     
  4. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,672
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    http://www.hiveworkshop.com/forums/spells-569/codeless-save-load-multiplayer-278664/?prev=status=p
    Syncing camera position
    Syncing GetLocationZ
    Sync return values of custom natives

    EDIT:

    Updated.

    Code (Text):

    v1.0.4

    - Reworked the string table to function properly
    - Can now auto-destroy instance when the syncing player leaves

    v1.0.3

    - Instance count increased from 512 > 8192
    - Fixed TimerUtils double free
    - Added refresh method to allow streaming of the same instance
    - Added support for real numbers
    - Minor efficiency improvements
     
    Here's an example on how to stream data without having to wait for all players to have received it.

    Code (vJASS):

    // this code will sync Player 2's camera location to Player 1's

    scope CameraTest initializer Init
       
        // i figured ints would be faster
       
        globals
            private real LastX = 0
            private real LastY = 0
            private integer LastIndex = 0
        endglobals

        // apply the camera
        private function ForceCam takes nothing returns nothing
            local SyncData d = SyncData(1)

            local real x
            local real y

            if (not d.hasInt(LastIndex) or not d.hasInt(LastIndex+1)) then
                return
            endif

            set x = d.readInt(LastIndex)
            set y = d.readInt(LastIndex+1)

            if (User.LocalId != 0 and (x != LastX or y != LastY)) then
                call PanCameraToTimed(x, y, 0.2)
            endif

            call ClearTextMessages()
            call BJDebugMsg(R2S(x) + ", " + R2S(y))

            set LastX=x
            set LastY=y
            set LastIndex=LastIndex+2
        endfunction

        // refresh once all players have recieved the current buffer
        private function onComplete takes nothing returns boolean
            local SyncData d = GetSyncedData()

            set LastIndex = 0

            call ForceCam()
            call d.refresh()
            call d.start()

            return false
        endfunction

        // update stream
        private function UpdateCam takes nothing returns nothing
            local SyncData d = SyncData(1)

            call d.addInt(R2I(GetCameraTargetPositionX()))
            call d.addInt(R2I(GetCameraTargetPositionY()))
        endfunction

        // begin stream
        private function StartTest takes nothing returns nothing
            local SyncData d = SyncData.create(Player(0))

            call d.addEventListener(function onComplete)
            call d.start()
        endfunction

        private function Init takes nothing returns nothing
            call TimerStart(CreateTimer(), 0, false, function StartTest)
            call TimerStart(CreateTimer(), 0.03, true, function UpdateCam)
            call TimerStart(CreateTimer(), 0.01, true, function ForceCam)
        endfunction

    endscope
     
     
    Last edited: May 16, 2016
  5. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Can you make PlayerUtils optional? Nobody uses that shit.

    Same goes for TimerUtils. Is there a specific reason that's in there?
     
  6. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,672
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Then fucking use it :thumbs_up:

    I guess I can make it optional but it's going to bloat the code.

    The library simply caches player data and keeps track of players. This way I can easily loop through only the playing players. It also use array lookups instead of native calls.

    Of course nobody uses it. I submitted it what, 2 weeks ago?

    EDIT: Overlooking the code I guess it could just be removed as a requirement. I mainly used it to speed up development time and for debugging, and since I recently just wrote it. I guess the performance increase is negligible. I also don't want to conflict with maps that use SetPlayerName and who do not want to convert their calls to the struct api, or have hooks enabled on that native.

    I have been considering removing TU. Originally I had large loops inside the callback function and looping through all indices could have reached OP limit easily.
     
  7. Troll-Brain

    Troll-Brain

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

    TriggerHappy

    Code Moderator

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

    I also added a Things to Know section in the documentation.
     
    Last edited: May 17, 2016
  9. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Made my day.
     
  10. TriggerHappy

    TriggerHappy

    Code Moderator

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

    Code (Text):

    v1.0.7

    - create method now properly returns 0 when the player isn't playing (or is a computer)
    - TimerUtils removed
    - Added support for JASS-like API with SyncInteger: TriggerRegisterSyncEvent(trigger, EVENT_SYNC_CACHE)
     
  11. TriggerHappy

    TriggerHappy

    Code Moderator

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

    Code (Text):

    v1.0.8

    - Now only uses 1 selection instead of 3 when notifying that the sync is over
    - Removed unused variables
     
     
  12. Waffle

    Waffle

    Joined:
    Jul 30, 2013
    Messages:
    271
    Resources:
    0
    Resources:
    0
    is it intentional that you MUST be logged in to read that linked bit?

    also i'm seriously considering adopting this lib.
     
  13. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,672
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    No I just didn't want to bloat the main post
     
  14. Waffle

    Waffle

    Joined:
    Jul 30, 2013
    Messages:
    271
    Resources:
    0
    Resources:
    0
    well the initial tests seem to be ok so FS just might get autoload some time soon :D
     
  15. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,188
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    constant integer JASS_MAX_ARRAY_SIZE
    could be used over MAX_STRINGS

    I tested this some weeks ago already and it is a great system with documentation.
    I will approve it soon, if nothing new comes up.
     
  16. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,672
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    But MAX_STRINGS isn't always going to be 8191, depending on what the user wants.
     
  17. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,188
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Sorry, it was a long jass day. You are right of course. :)
     
  18. Hey is the speed enough for this:

    There's dozens of good lossless compression types for pictures; a library that utilizes Sync, FileIO and one such algorithm to give out a 2D representation of a map is my dream.

    Maps could then be loaded from user's hard drive.

    Edit: Already something as simple as pattern inspection saves lots of space. But if someone here is actually experienced in lossless data compression, I would love to hear his solution.
     
    Last edited by a moderator: Aug 7, 2016
  19. TriggerHappy

    TriggerHappy

    Code Moderator

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

    v1.0.9
    • SyncStoredX functions are now called from within the start method as opposed to from the addX methods.
    • The start method is ran in a new thread to prevent hitting the OP limit.
    I don't know it seems to throttle around 1kb/s. I don't really understand your idea though.

    Anyway, can this get approved?
     
    Last edited: Sep 11, 2016
  20. TriggerHappy

    TriggerHappy

    Code Moderator

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

    v.1.1.0
    • Fixed a bug that could cause events to fire in the wrong order (desync).
    • Game Cache keys now get stored in the hashtable to improve performance.
    • Added option to preload Game Cache keys.
    • hasString reworked.
    • Removed unnecessary loops.