• 🏆 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] BitBuf

Level 13
Joined
Nov 7, 2014
Messages
571
BitBuf - packs integers into a bit buffer -> encodes to base64 -> decodes from base64 -> unpacks integers from a bit buffer

JASS:
library BitBuf requires Bitops

//! novjass

BitBuf - packs integers into a bit buffer -> encodes to base64 -> decodes from base64 -> unpacks integers from a bit buffer

requires:
    Bitops: http://www.hiveworkshop.com/threads/bitops.274878/


API:

//
// Creating/Destroying a BitBuf
//

// Creates a BitBuf of size `size_in_bits` that can be written to.
//
static method create takes integer size_in_bits returns BitBuf
method destroy takes nothing returns nothing

// Creates a BitBuf from a base64 encoded string that can be read from.
//
static method from_base64 takes string base64 returns BitBuf


//
// Writing to a BitBuf
//

// Instead of creating and destroying a BitBuf it can be created/initialized
// once and be written to over and over again after calling this method.
method begin_writing takes nothing returns nothing

// BitBuf.from_base64 calls this method automatically.
// You can call it manually after writing to the BitBuf.
method begin_reading takes nothing returns nothing

// Writing unsigned (>= 0) integers.
// Note: the notation: a .. b denotes an inclusive on both ends range.
// Ranges are checked only in debug mode.

method write_u1 takes integer u1 returns nothing // range: 0 .. 1
method write_u2 takes integer u2 returns nothing // range: 0 .. 3
method write_u3 takes integer u3 returns nothing // range: 0 .. 7
method write_u4 takes integer u4 returns nothing // range: 0 .. 15
method write_u5 takes integer u5 returns nothing // range: 0 .. 31
method write_u6 takes integer u6 returns nothing // range: 0 .. 63
method write_u7 takes integer u7 returns nothing // range: 0 .. 127
method write_u8 takes integer u8 returns nothing // range: 0 .. 255
method write_u9 takes integer u9 returns nothing // range: 0 .. 511
method write_u10 takes integer u10 returns nothing // range: 0 .. 1023
method write_u11 takes integer u11 returns nothing // range: 0 .. 2047
method write_u12 takes integer u12 returns nothing // range: 0 .. 4095
method write_u13 takes integer u13 returns nothing // range: 0 .. 8191
method write_u14 takes integer u14 returns nothing // range: 0 .. 16383
method write_u15 takes integer u15 returns nothing // range: 0 .. 32767
method write_u16 takes integer u16 returns nothing // range: 0 .. 65535
method write_u17 takes integer u17 returns nothing // range: 0 .. 131071
method write_u18 takes integer u18 returns nothing // range: 0 .. 262143
method write_u19 takes integer u19 returns nothing // range: 0 .. 524287
method write_u20 takes integer u20 returns nothing // range: 0 .. 1048575
method write_u21 takes integer u21 returns nothing // range: 0 .. 2097151
method write_u22 takes integer u22 returns nothing // range: 0 .. 4194303
method write_u23 takes integer u23 returns nothing // range: 0 .. 8388607
method write_u24 takes integer u24 returns nothing // range: 0 .. 16777215
method write_u25 takes integer u25 returns nothing // range: 0 .. 33554431
method write_u26 takes integer u26 returns nothing // range: 0 .. 67108863
method write_u27 takes integer u27 returns nothing // range: 0 .. 134217727
method write_u28 takes integer u28 returns nothing // range: 0 .. 268435455
method write_u29 takes integer u29 returns nothing // range: 0 .. 536870911
method write_u30 takes integer u30 returns nothing // range: 0 .. 1073741823
method write_u31 takes integer u31 returns nothing // range: 0 .. 2147483647
// Note: there is no write_u32 because Jass has only signed 32 bit integers


// Writing signed integers

// Note: there is no write_s1 method
method write_s2 takes integer s2 returns nothing // range: -2 .. 1
method write_s3 takes integer s3 returns nothing // range: -4 .. 3
method write_s4 takes integer s4 returns nothing // range: -8 .. 7
method write_s5 takes integer s5 returns nothing // range: -16 .. 15
method write_s6 takes integer s6 returns nothing // range: -32 .. 31
method write_s7 takes integer s7 returns nothing // range: -64 .. 63
method write_s8 takes integer s8 returns nothing // range: -128 .. 127
method write_s9 takes integer s9 returns nothing // range: -256 .. 255
method write_s10 takes integer s10 returns nothing // range: -512 .. 511
method write_s11 takes integer s11 returns nothing // range: -1024 .. 1023
method write_s12 takes integer s12 returns nothing // range: -2048 .. 2047
method write_s13 takes integer s13 returns nothing // range: -4096 .. 4095
method write_s14 takes integer s14 returns nothing // range: -8192 .. 8191
method write_s15 takes integer s15 returns nothing // range: -16384 .. 16383
method write_s16 takes integer s16 returns nothing // range: -32768 .. 32767
method write_s17 takes integer s17 returns nothing // range: -65536 .. 65535
method write_s18 takes integer s18 returns nothing // range: -131072 .. 131071
method write_s19 takes integer s19 returns nothing // range: -262144 .. 262143
method write_s20 takes integer s20 returns nothing // range: -524288 .. 524287
method write_s21 takes integer s21 returns nothing // range: -1048576 .. 1048575
method write_s22 takes integer s22 returns nothing // range: -2097152 .. 2097151
method write_s23 takes integer s23 returns nothing // range: -4194304 .. 4194303
method write_s24 takes integer s24 returns nothing // range: -8388608 .. 8388607
method write_s25 takes integer s25 returns nothing // range: -16777216 .. 16777215
method write_s26 takes integer s26 returns nothing // range: -33554432 .. 33554431
method write_s27 takes integer s27 returns nothing // range: -67108864 .. 67108863
method write_s28 takes integer s28 returns nothing // range: -134217728 .. 134217727
method write_s29 takes integer s29 returns nothing // range: -268435456 .. 268435455
method write_s30 takes integer s30 returns nothing // range: -536870912 .. 536870911
method write_s31 takes integer s31 returns nothing // range: -1073741824 .. 1073741823
method write_s32 takes integer s32 returns nothing // range: -2147483648 .. 2147483647

// Writing reals

// Note: for these methods the ENABLE_REAL_READ_WRITE configuration variable must be set to true

// Chops the lower 16 bits of the mantisa first and then writes the remaning 16 to the bit buffer.
method write_r16 takes real r32 returns nothing

method write_r32 takes real r32 returns nothing


// Writing booleans

// Writies a single bit that's set to 1 if the boolean is true, 0 otherwise.
method write_bool takes boolean b returns nothing


//
// Reading from a BitBuf
//

// Note: if an attempt is made to read pass the end of the buffer the script execution stops and an
// error is shown (in debug mode only).

// Reading unsigned (>= 0) integers

method read_u1 takes nothing returns integer
method read_u2 takes nothing returns integer
method read_u3 takes nothing returns integer
method read_u4 takes nothing returns integer
method read_u5 takes nothing returns integer
method read_u6 takes nothing returns integer
method read_u7 takes nothing returns integer
method read_u8 takes nothing returns integer
method read_u9 takes nothing returns integer
method read_u10 takes nothing returns integer
method read_u11 takes nothing returns integer
method read_u12 takes nothing returns integer
method read_u13 takes nothing returns integer
method read_u14 takes nothing returns integer
method read_u15 takes nothing returns integer
method read_u16 takes nothing returns integer
method read_u17 takes nothing returns integer
method read_u18 takes nothing returns integer
method read_u19 takes nothing returns integer
method read_u20 takes nothing returns integer
method read_u21 takes nothing returns integer
method read_u22 takes nothing returns integer
method read_u23 takes nothing returns integer
method read_u24 takes nothing returns integer
method read_u25 takes nothing returns integer
method read_u26 takes nothing returns integer
method read_u27 takes nothing returns integer
method read_u28 takes nothing returns integer
method read_u29 takes nothing returns integer
method read_u30 takes nothing returns integer
method read_u31 takes nothing returns integer

// Reading signed ntegers

method read_s2 takes nothing returns integer
method read_s3 takes nothing returns integer
method read_s4 takes nothing returns integer
method read_s5 takes nothing returns integer
method read_s6 takes nothing returns integer
method read_s7 takes nothing returns integer
method read_s8 takes nothing returns integer
method read_s9 takes nothing returns integer
method read_s10 takes nothing returns integer
method read_s11 takes nothing returns integer
method read_s12 takes nothing returns integer
method read_s13 takes nothing returns integer
method read_s14 takes nothing returns integer
method read_s15 takes nothing returns integer
method read_s16 takes nothing returns integer
method read_s17 takes nothing returns integer
method read_s18 takes nothing returns integer
method read_s19 takes nothing returns integer
method read_s20 takes nothing returns integer
method read_s21 takes nothing returns integer
method read_s22 takes nothing returns integer
method read_s23 takes nothing returns integer
method read_s24 takes nothing returns integer
method read_s25 takes nothing returns integer
method read_s26 takes nothing returns integer
method read_s27 takes nothing returns integer
method read_s28 takes nothing returns integer
method read_s29 takes nothing returns integer
method read_s30 takes nothing returns integer
method read_s31 takes nothing returns integer
method read_s32 takes nothing returns integer


// Reading reals

// Note: for these methods the ENABLE_REAL_READ_WRITE configuration variable must be set to true
method read_r16 takes nothing returns real
method read_r32 takes nothing returns real


// Reading booleans

// Reads a single bit if it's 1 returns true, false otherwise
method read_bool takes nothing returns boolean


//
// Encoding to base64
//

// Encodes the bits that were previously written to the buffer as a base64 string.
//
method to_base64 takes nothing returns string

// Applies a color to each class of digit
//
// uppercase letters: A .. Z, color: uc
// lowercase letters: a .. z, color: lc
// arabic numerals: 0 .. 9, color d
// '+' and '/': color p
//
static method base64_apply_colors takes string base64, string uc, string lc, string d, string p returns string


//
// Simple checksum
//

// Generates a check[sum] digit using the Luhn mod N algorithm
//
static method luhn_generate_check_digit takes string base64 returns string

// Validates the base64 encoded string against its check digit (last character)
//
static method luhn_is_valid takes string base64 returns boolean

//! endnovjass


globals
    // In order to write/read reals we need to type cast them to integers first
    // but this type casting is done by "tricking" the VM with a pretty clever trick
    // but it might get fixed in upcoming patches (current version is: 1.27.0).
    // Besides, reals take a lot of bits which could be saved for other things if the encoding is "smart".
    private constant boolean ENABLE_REAL_READ_WRITE = false
endglobals

static if ENABLE_REAL_READ_WRITE then

    //# +nosemanticerror
    private function r2i takes real r returns integer
        return r
    endfunction

    private function clean_int takes integer i returns integer
        return i
        return 0
    endfunction

    //# +nosemanticerror
    private function i2r takes integer i returns real
        return i
    endfunction

    private function clean_real takes real r returns real
        return r
        return 0.0
    endfunction

    public function real_as_int takes real r returns integer
        return clean_int(r2i(r))
    endfunction

    public function int_as_real takes integer i returns real
        return clean_real(i2r(i))
    endfunction

endif


public function ceil takes real r returns integer
    local integer i = R2I(r)

    if r == i then
        return i
    endif

    if r > 0.0 then
        return i + 1
    else // if r < 0 then
        return i
    endif
endfunction

public function round_up_nearest takes integer n, integer m returns integer
    return ceil(n / I2R(m)) * m
endfunction

public function size_in_bits_to_size_in_words takes integer size_in_bits returns integer
    return round_up_nearest(size_in_bits, 32) / 32
endfunction

private function panic takes string msg returns nothing
    local integer i = 0
    call BJDebugMsg("|cffFF0000BitBuf error: " + msg + "|r")
    set i = 1 / 0 // we report the error and now we stop the script from running any further
endfunction

struct BitBuf extends array
    private static integer bit_buf_alloc = 0
     // heads of the free lists of BitBuf(s):
     //     heads[1] -- the head of the linked of BitBuf(s) with size_in_words = 1
     //     heads[2] -- ... with size_in_words = 2
     //     ...
    private static thistype array heads
    private thistype next

    private static string array b64_encode_table
    private static integer b64_encode_index = 0
    private static hashtable b64_decode_table

    readonly static integer array word_buf
    readonly static integer word_buf_p = 0

    readonly integer wbo // word_buf offset
    readonly integer wp // the position of the current word starting from wbo, each word is 4 bytes (32 bits)
    readonly integer bp // the position of the current bit in the current word, bits are written/read from left to right

    readonly integer bits_written
    readonly integer bits_read
    readonly integer size_in_bits
    readonly integer size_in_words // 1 word = 32 bits

    method clear takes nothing returns nothing
        local integer i = this.wbo
        local integer j = this.wbo + this.size_in_words
        loop
            exitwhen i >= j
            set word_buf[i] = 0
            set i = i + 1
        endloop
    endmethod

    static method create takes integer p_size_in_bits returns thistype
        local thistype this
        local integer i
        local integer j
        local integer l_size_in_words

static if DEBUG_MODE then
        if p_size_in_bits <= 0 then
            call panic("size_in_bits must be > 0")
        endif
endif
        set l_size_in_words = size_in_bits_to_size_in_words(p_size_in_bits)

        if heads[l_size_in_words] != 0 then
            set this = heads[l_size_in_words]
            set heads[l_size_in_words] = heads[l_size_in_words].next

        else
static if DEBUG_MODE then
            if bit_buf_alloc + 1 > 8190 or word_buf_p + l_size_in_words > 8190 then
                call panic("could not create a new BitBuf")
            endif
endif
            set bit_buf_alloc = bit_buf_alloc + 1
            set this = bit_buf_alloc

            set this.wbo = word_buf_p
            set word_buf_p = word_buf_p + l_size_in_words
        endif

        set this.wp = 0
        set this.bp = 0
        set this.bits_written = 0
        set this.bits_read = 0
        set this.size_in_bits = p_size_in_bits
        set this.size_in_words = l_size_in_words

        call this.clear()

        return this
    endmethod

    method destroy takes nothing returns nothing
        set this.next = heads[this.size_in_words]
        set heads[this.size_in_words] = this
    endmethod

    private method cache_state takes nothing returns nothing
        local thistype cache = thistype(0)

        set cache.wp = this.wp
        set cache.bp = this.bp
        set cache.bits_written = this.bits_written
        set cache.bits_read = this.bits_read
    endmethod
    private method restore_state_from_cache takes nothing returns nothing
        local thistype cache = thistype(0)

        set this.wp = cache.wp
        set this.bp = cache.bp
        set this.bits_written = cache.bits_written
        set this.bits_read = cache.bits_read
    endmethod


    method begin_writing takes nothing returns nothing
        set this.wp = 0
        set this.bp = 0
        set this.bits_written = 0
        call this.clear()
    endmethod

    method write_bits takes integer bits, integer bits_count returns nothing
        local integer index
        local integer v
        local integer g1_bits_count
        local integer g2_bits_count
        local integer g1_bits
        local integer g2_bits

        set this.bits_written = this.bits_written + bits_count
static if DEBUG_MODE then
        if this.bits_written > this.size_in_bits then
            call panic("attempting to write " + I2S(this.bits_written) + " bits but BitBuf(" + I2S(this) + ").size_in_bits is " + I2S(this.size_in_bits))
        endif
endif

        if this.bp + bits_count > 32 then
            // we can't write all the bits to the current word
            // but we can split them into two groups, the 1st
            // will have the bits that can be written to the current word
            // and the 2nd will have the rest of the bits
            //
            set g1_bits_count = 32 - this.bp
            set g2_bits_count = bits_count - g1_bits_count

            set g1_bits = SHR(bits, g2_bits_count)
            set index = this.wbo + this.wp
            set v = word_buf[index]
            set v = v + SHL(g1_bits, 32 - g1_bits_count - this.bp)
            set word_buf[index] = v

            set this.wp = this.wp + 1
            set this.bp = 0

            set g2_bits = SHL(bits, 32 - g2_bits_count)
            set g2_bits = SHR(g2_bits, 32 - g2_bits_count)
            set index = this.wbo + this.wp
            set v = word_buf[index]
            set v = v + SHL(g2_bits, 32 - g2_bits_count - this.bp)
            set word_buf[index] = v

            set this.bp = this.bp + g2_bits_count

        else
            set index = this.wbo + this.wp
            set v = word_buf[index]
            set v = v + SHL(bits, 32 - bits_count - this.bp)
            set word_buf[index] = v

            set this.bp = this.bp + bits_count
        endif
    endmethod

    method write_u1 takes integer u1 returns nothing
        debug if not (0x0 <= u1 and u1 <= 0x1) then
        debug    call panic("write_u1 range is 0 .. 1; value is " + I2S(u1))
        debug endif
        call this.write_bits(u1, 1)
    endmethod
    method write_u2 takes integer u2 returns nothing
        debug if not (0x0 <= u2 and u2 <= 0x3) then
        debug    call panic("write_u2 range is 0 .. 3; value is " + I2S(u2))
        debug endif
        call this.write_bits(u2, 2)
    endmethod
    method write_u3 takes integer u3 returns nothing
        debug if not (0x0 <= u3 and u3 <= 0x7) then
        debug    call panic("write_u3 range is 0 .. 7; value is " + I2S(u3))
        debug endif
        call this.write_bits(u3, 3)
    endmethod
    method write_u4 takes integer u4 returns nothing
        debug if not (0x0 <= u4 and u4 <= 0xF) then
        debug    call panic("write_u4 range is 0 .. 15; value is " + I2S(u4))
        debug endif
        call this.write_bits(u4, 4)
    endmethod
    method write_u5 takes integer u5 returns nothing
        debug if not (0x0 <= u5 and u5 <= 0x1F) then
        debug    call panic("write_u5 range is 0 .. 31; value is " + I2S(u5))
        debug endif
        call this.write_bits(u5, 5)
    endmethod
    method write_u6 takes integer u6 returns nothing
        debug if not (0x0 <= u6 and u6 <= 0x3F) then
        debug    call panic("write_u6 range is 0 .. 63; value is " + I2S(u6))
        debug endif
        call this.write_bits(u6, 6)
    endmethod
    method write_u7 takes integer u7 returns nothing
        debug if not (0x0 <= u7 and u7 <= 0x7F) then
        debug    call panic("write_u7 range is 0 .. 127; value is " + I2S(u7))
        debug endif
        call this.write_bits(u7, 7)
    endmethod
    method write_u8 takes integer u8 returns nothing
        debug if not (0x0 <= u8 and u8 <= 0xFF) then
        debug    call panic("write_u8 range is 0 .. 255; value is " + I2S(u8))
        debug endif
        call this.write_bits(u8, 8)
    endmethod
    method write_u9 takes integer u9 returns nothing
        debug if not (0x0 <= u9 and u9 <= 0x1FF) then
        debug    call panic("write_u9 range is 0 .. 511; value is " + I2S(u9))
        debug endif
        call this.write_bits(u9, 9)
    endmethod
    method write_u10 takes integer u10 returns nothing
        debug if not (0x0 <= u10 and u10 <= 0x3FF) then
        debug    call panic("write_u10 range is 0 .. 1023; value is " + I2S(u10))
        debug endif
        call this.write_bits(u10, 10)
    endmethod
    method write_u11 takes integer u11 returns nothing
        debug if not (0x0 <= u11 and u11 <= 0x7FF) then
        debug    call panic("write_u11 range is 0 .. 2047; value is " + I2S(u11))
        debug endif
        call this.write_bits(u11, 11)
    endmethod
    method write_u12 takes integer u12 returns nothing
        debug if not (0x0 <= u12 and u12 <= 0xFFF) then
        debug    call panic("write_u12 range is 0 .. 4095; value is " + I2S(u12))
        debug endif
        call this.write_bits(u12, 12)
    endmethod
    method write_u13 takes integer u13 returns nothing
        debug if not (0x0 <= u13 and u13 <= 0x1FFF) then
        debug    call panic("write_u13 range is 0 .. 8191; value is " + I2S(u13))
        debug endif
        call this.write_bits(u13, 13)
    endmethod
    method write_u14 takes integer u14 returns nothing
        debug if not (0x0 <= u14 and u14 <= 0x3FFF) then
        debug    call panic("write_u14 range is 0 .. 16383; value is " + I2S(u14))
        debug endif
        call this.write_bits(u14, 14)
    endmethod
    method write_u15 takes integer u15 returns nothing
        debug if not (0x0 <= u15 and u15 <= 0x7FFF) then
        debug    call panic("write_u15 range is 0 .. 32767; value is " + I2S(u15))
        debug endif
        call this.write_bits(u15, 15)
    endmethod
    method write_u16 takes integer u16 returns nothing
        debug if not (0x0 <= u16 and u16 <= 0xFFFF) then
        debug    call panic("write_u16 range is 0 .. 65535; value is " + I2S(u16))
        debug endif
        call this.write_bits(u16, 16)
    endmethod
    method write_u17 takes integer u17 returns nothing
        debug if not (0x0 <= u17 and u17 <= 0x1FFFF) then
        debug    call panic("write_u17 range is 0 .. 131071; value is " + I2S(u17))
        debug endif
        call this.write_bits(u17, 17)
    endmethod
    method write_u18 takes integer u18 returns nothing
        debug if not (0x0 <= u18 and u18 <= 0x3FFFF) then
        debug    call panic("write_u18 range is 0 .. 262143; value is " + I2S(u18))
        debug endif
        call this.write_bits(u18, 18)
    endmethod
    method write_u19 takes integer u19 returns nothing
        debug if not (0x0 <= u19 and u19 <= 0x7FFFF) then
        debug    call panic("write_u19 range is 0 .. 524287; value is " + I2S(u19))
        debug endif
        call this.write_bits(u19, 19)
    endmethod
    method write_u20 takes integer u20 returns nothing
        debug if not (0x0 <= u20 and u20 <= 0xFFFFF) then
        debug    call panic("write_u20 range is 0 .. 1048575; value is " + I2S(u20))
        debug endif
        call this.write_bits(u20, 20)
    endmethod
    method write_u21 takes integer u21 returns nothing
        debug if not (0x0 <= u21 and u21 <= 0x1FFFFF) then
        debug    call panic("write_u21 range is 0 .. 2097151; value is " + I2S(u21))
        debug endif
        call this.write_bits(u21, 21)
    endmethod
    method write_u22 takes integer u22 returns nothing
        debug if not (0x0 <= u22 and u22 <= 0x3FFFFF) then
        debug    call panic("write_u22 range is 0 .. 4194303; value is " + I2S(u22))
        debug endif
        call this.write_bits(u22, 22)
    endmethod
    method write_u23 takes integer u23 returns nothing
        debug if not (0x0 <= u23 and u23 <= 0x7FFFFF) then
        debug    call panic("write_u23 range is 0 .. 8388607; value is " + I2S(u23))
        debug endif
        call this.write_bits(u23, 23)
    endmethod
    method write_u24 takes integer u24 returns nothing
        debug if not (0x0 <= u24 and u24 <= 0xFFFFFF) then
        debug    call panic("write_u24 range is 0 .. 16777215; value is " + I2S(u24))
        debug endif
        call this.write_bits(u24, 24)
    endmethod
    method write_u25 takes integer u25 returns nothing
        debug if not (0x0 <= u25 and u25 <= 0x1FFFFFF) then
        debug    call panic("write_u25 range is 0 .. 33554431; value is " + I2S(u25))
        debug endif
        call this.write_bits(u25, 25)
    endmethod
    method write_u26 takes integer u26 returns nothing
        debug if not (0x0 <= u26 and u26 <= 0x3FFFFFF) then
        debug    call panic("write_u26 range is 0 .. 67108863; value is " + I2S(u26))
        debug endif
        call this.write_bits(u26, 26)
    endmethod
    method write_u27 takes integer u27 returns nothing
        debug if not (0x0 <= u27 and u27 <= 0x7FFFFFF) then
        debug    call panic("write_u27 range is 0 .. 134217727; value is " + I2S(u27))
        debug endif
        call this.write_bits(u27, 27)
    endmethod
    method write_u28 takes integer u28 returns nothing
        debug if not (0x0 <= u28 and u28 <= 0xFFFFFFF) then
        debug    call panic("write_u28 range is 0 .. 268435455; value is " + I2S(u28))
        debug endif
        call this.write_bits(u28, 28)
    endmethod
    method write_u29 takes integer u29 returns nothing
        debug if not (0x0 <= u29 and u29 <= 0x1FFFFFFF) then
        debug    call panic("write_u29 range is 0 .. 536870911; value is " + I2S(u29))
        debug endif
        call this.write_bits(u29, 29)
    endmethod
    method write_u30 takes integer u30 returns nothing
        debug if not (0x0 <= u30 and u30 <= 0x3FFFFFFF) then
        debug    call panic("write_u30 range is 0 .. 1073741823; value is " + I2S(u30))
        debug endif
        call this.write_bits(u30, 30)
    endmethod
    method write_u31 takes integer u31 returns nothing
        debug if not (0x0 <= u31 and u31 <= 0x7FFFFFFF) then
        debug    call panic("write_u31 range is 0 .. 2147483647; value is " + I2S(u31))
        debug endif
        call this.write_bits(u31, 31)
    endmethod


    method write_signed_bits takes integer bits, integer bits_count returns nothing
        if bits < 0 then
            call this.write_bits(0x1, 1)
            set bits = SHL(bits, 33 - bits_count)
            set bits = SHR(bits, 33 - bits_count)

        else
            call this.write_bits(0x0, 1)
        endif
        call this.write_bits(bits, bits_count - 1)
    endmethod

    method write_s2 takes integer s2 returns nothing
        debug if not (0xFFFFFFFE <= s2 and s2 <= 0x1) then
        debug    call panic("write_s2 range is -2 .. 1; value is " + I2S(s2))
        debug endif
        call this.write_signed_bits(s2, 2)
    endmethod
    method write_s3 takes integer s3 returns nothing
        debug if not (0xFFFFFFFC <= s3 and s3 <= 0x3) then
        debug    call panic("write_s3 range is -4 .. 3; value is " + I2S(s3))
        debug endif
        call this.write_signed_bits(s3, 3)
    endmethod
    method write_s4 takes integer s4 returns nothing
        debug if not (0xFFFFFFF8 <= s4 and s4 <= 0x7) then
        debug    call panic("write_s4 range is -8 .. 7; value is " + I2S(s4))
        debug endif
        call this.write_signed_bits(s4, 4)
    endmethod
    method write_s5 takes integer s5 returns nothing
        debug if not (0xFFFFFFF0 <= s5 and s5 <= 0xF) then
        debug    call panic("write_s5 range is -16 .. 15; value is " + I2S(s5))
        debug endif
        call this.write_signed_bits(s5, 5)
    endmethod
    method write_s6 takes integer s6 returns nothing
        debug if not (0xFFFFFFE0 <= s6 and s6 <= 0x1F) then
        debug    call panic("write_s6 range is -32 .. 31; value is " + I2S(s6))
        debug endif
        call this.write_signed_bits(s6, 6)
    endmethod
    method write_s7 takes integer s7 returns nothing
        debug if not (0xFFFFFFC0 <= s7 and s7 <= 0x3F) then
        debug    call panic("write_s7 range is -64 .. 63; value is " + I2S(s7))
        debug endif
        call this.write_signed_bits(s7, 7)
    endmethod
    method write_s8 takes integer s8 returns nothing
        debug if not (0xFFFFFF80 <= s8 and s8 <= 0x7F) then
        debug    call panic("write_s8 range is -128 .. 127; value is " + I2S(s8))
        debug endif
        call this.write_signed_bits(s8, 8)
    endmethod
    method write_s9 takes integer s9 returns nothing
        debug if not (0xFFFFFF00 <= s9 and s9 <= 0xFF) then
        debug    call panic("write_s9 range is -256 .. 255; value is " + I2S(s9))
        debug endif
        call this.write_signed_bits(s9, 9)
    endmethod
    method write_s10 takes integer s10 returns nothing
        debug if not (0xFFFFFE00 <= s10 and s10 <= 0x1FF) then
        debug    call panic("write_s10 range is -512 .. 511; value is " + I2S(s10))
        debug endif
        call this.write_signed_bits(s10, 10)
    endmethod
    method write_s11 takes integer s11 returns nothing
        debug if not (0xFFFFFC00 <= s11 and s11 <= 0x3FF) then
        debug    call panic("write_s11 range is -1024 .. 1023; value is " + I2S(s11))
        debug endif
        call this.write_signed_bits(s11, 11)
    endmethod
    method write_s12 takes integer s12 returns nothing
        debug if not (0xFFFFF800 <= s12 and s12 <= 0x7FF) then
        debug    call panic("write_s12 range is -2048 .. 2047; value is " + I2S(s12))
        debug endif
        call this.write_signed_bits(s12, 12)
    endmethod
    method write_s13 takes integer s13 returns nothing
        debug if not (0xFFFFF000 <= s13 and s13 <= 0xFFF) then
        debug    call panic("write_s13 range is -4096 .. 4095; value is " + I2S(s13))
        debug endif
        call this.write_signed_bits(s13, 13)
    endmethod
    method write_s14 takes integer s14 returns nothing
        debug if not (0xFFFFE000 <= s14 and s14 <= 0x1FFF) then
        debug    call panic("write_s14 range is -8192 .. 8191; value is " + I2S(s14))
        debug endif
        call this.write_signed_bits(s14, 14)
    endmethod
    method write_s15 takes integer s15 returns nothing
        debug if not (0xFFFFC000 <= s15 and s15 <= 0x3FFF) then
        debug    call panic("write_s15 range is -16384 .. 16383; value is " + I2S(s15))
        debug endif
        call this.write_signed_bits(s15, 15)
    endmethod
    method write_s16 takes integer s16 returns nothing
        debug if not (0xFFFF8000 <= s16 and s16 <= 0x7FFF) then
        debug    call panic("write_s16 range is -32768 .. 32767; value is " + I2S(s16))
        debug endif
        call this.write_signed_bits(s16, 16)
    endmethod
    method write_s17 takes integer s17 returns nothing
        debug if not (0xFFFF0000 <= s17 and s17 <= 0xFFFF) then
        debug    call panic("write_s17 range is -65536 .. 65535; value is " + I2S(s17))
        debug endif
        call this.write_signed_bits(s17, 17)
    endmethod
    method write_s18 takes integer s18 returns nothing
        debug if not (0xFFFE0000 <= s18 and s18 <= 0x1FFFF) then
        debug    call panic("write_s18 range is -131072 .. 131071; value is " + I2S(s18))
        debug endif
        call this.write_signed_bits(s18, 18)
    endmethod
    method write_s19 takes integer s19 returns nothing
        debug if not (0xFFFC0000 <= s19 and s19 <= 0x3FFFF) then
        debug    call panic("write_s19 range is -262144 .. 262143; value is " + I2S(s19))
        debug endif
        call this.write_signed_bits(s19, 19)
    endmethod
    method write_s20 takes integer s20 returns nothing
        debug if not (0xFFF80000 <= s20 and s20 <= 0x7FFFF) then
        debug    call panic("write_s20 range is -524288 .. 524287; value is " + I2S(s20))
        debug endif
        call this.write_signed_bits(s20, 20)
    endmethod
    method write_s21 takes integer s21 returns nothing
        debug if not (0xFFF00000 <= s21 and s21 <= 0xFFFFF) then
        debug    call panic("write_s21 range is -1048576 .. 1048575; value is " + I2S(s21))
        debug endif
        call this.write_signed_bits(s21, 21)
    endmethod
    method write_s22 takes integer s22 returns nothing
        debug if not (0xFFE00000 <= s22 and s22 <= 0x1FFFFF) then
        debug    call panic("write_s22 range is -2097152 .. 2097151; value is " + I2S(s22))
        debug endif
        call this.write_signed_bits(s22, 22)
    endmethod
    method write_s23 takes integer s23 returns nothing
        debug if not (0xFFC00000 <= s23 and s23 <= 0x3FFFFF) then
        debug    call panic("write_s23 range is -4194304 .. 4194303; value is " + I2S(s23))
        debug endif
        call this.write_signed_bits(s23, 23)
    endmethod
    method write_s24 takes integer s24 returns nothing
        debug if not (0xFF800000 <= s24 and s24 <= 0x7FFFFF) then
        debug    call panic("write_s24 range is -8388608 .. 8388607; value is " + I2S(s24))
        debug endif
        call this.write_signed_bits(s24, 24)
    endmethod
    method write_s25 takes integer s25 returns nothing
        debug if not (0xFF000000 <= s25 and s25 <= 0xFFFFFF) then
        debug    call panic("write_s25 range is -16777216 .. 16777215; value is " + I2S(s25))
        debug endif
        call this.write_signed_bits(s25, 25)
    endmethod
    method write_s26 takes integer s26 returns nothing
        debug if not (0xFE000000 <= s26 and s26 <= 0x1FFFFFF) then
        debug    call panic("write_s26 range is -33554432 .. 33554431; value is " + I2S(s26))
        debug endif
        call this.write_signed_bits(s26, 26)
    endmethod
    method write_s27 takes integer s27 returns nothing
        debug if not (0xFC000000 <= s27 and s27 <= 0x3FFFFFF) then
        debug    call panic("write_s27 range is -67108864 .. 67108863; value is " + I2S(s27))
        debug endif
        call this.write_signed_bits(s27, 27)
    endmethod
    method write_s28 takes integer s28 returns nothing
        debug if not (0xF8000000 <= s28 and s28 <= 0x7FFFFFF) then
        debug    call panic("write_s28 range is -134217728 .. 134217727; value is " + I2S(s28))
        debug endif
        call this.write_signed_bits(s28, 28)
    endmethod
    method write_s29 takes integer s29 returns nothing
        debug if not (0xF0000000 <= s29 and s29 <= 0xFFFFFFF) then
        debug    call panic("write_s29 range is -268435456 .. 268435455; value is " + I2S(s29))
        debug endif
        call this.write_signed_bits(s29, 29)
    endmethod
    method write_s30 takes integer s30 returns nothing
        debug if not (0xE0000000 <= s30 and s30 <= 0x1FFFFFFF) then
        debug    call panic("write_s30 range is -536870912 .. 536870911; value is " + I2S(s30))
        debug endif
        call this.write_signed_bits(s30, 30)
    endmethod
    method write_s31 takes integer s31 returns nothing
        debug if not (0xC0000000 <= s31 and s31 <= 0x3FFFFFFF) then
        debug    call panic("write_s31 range is -1073741824 .. 1073741823; value is " + I2S(s31))
        debug endif
        call this.write_signed_bits(s31, 31)
    endmethod
    method write_s32 takes integer s32 returns nothing
        // debug if not (0x80000000 <= s32 and s32 <= 0x7FFFFFFF) then
        // debug    call panic("write_s32 range is -2147483648 .. 2147483647; value is " + I2S(s32))
        // debug endif
        call this.write_signed_bits(s32, 32)
    endmethod


static if ENABLE_REAL_READ_WRITE then

    method write_r16 takes real r32 returns nothing
        local integer u32 = real_as_int(r32)
        local integer u16 = SHR(u32, 16) // chop the lower 16 bits of the mantisa
        this.write_u16(u16)
    endmethod

    methd write_r32 takes real r32 returns nothing
        local integer s32 = real_as_int(r32)
        call this.write_s32(s32)
    endmethod

endif

    method write_bool takes boolean b returns nothing
        if b then
            call this.write_u1(0x1)
        else
            call this.write_u1(0x0)
        endif
    endmethod


    method begin_reading takes nothing returns nothing
        set this.wp = 0
        set this.bp = 0
        set this.bits_read = 0
    endmethod

    method read_bits takes integer bits_count returns integer
        local integer index
        local integer v
        local integer g1_bits_count
        local integer g2_bits_count
        local integer g1_bits
        local integer g2_bits
        local integer result_bits = 0

        set this.bits_read = this.bits_read + bits_count
static if DEBUG_MODE then
        if this.bits_read > this.size_in_bits then
            call panic("attempting to read " + I2S(this.bits_read) + " bits but BitBuf(" + I2S(this) + ").size_in_bits is " + I2S(this.size_in_bits))
        endif
endif

        if this.bp + bits_count > 32 then
            // we can't read all the bits from the current word
            // but we can split them into two groups, the 1st
            // will have the bits that can be read from the current word
            // and the 2nd will have the rest of the bits
            //
            set g1_bits_count = 32 - this.bp
            set g2_bits_count = bits_count - g1_bits_count

            set index = this.wbo + this.wp
            set v = word_buf[index]
            set g1_bits = SHL(v, this.bp)
            set g1_bits = SHR(g1_bits, this.bp)
            set result_bits = result_bits + SHL(g1_bits, g2_bits_count)

            set this.wp = this.wp + 1
            set this.bp = 0

            set index = this.wbo + this.wp
            set v = word_buf[index]
            set g2_bits = SHR(v, 32 - g2_bits_count)
            set result_bits = result_bits + g2_bits

            set this.bp = this.bp + g2_bits_count

        else
            set index = this.wbo + this.wp
            set v = word_buf[index]
            set result_bits = SHL(v, this.bp)
            set result_bits = SHR(result_bits, 32 - bits_count)

            set this.bp = this.bp + bits_count
            if this.bp == 32 then
                set this.wp = this.wp + 1
                set this.bp = 0
            endif

        endif

        return result_bits
    endmethod

    method read_u1 takes nothing returns integer
        return this.read_bits(1)
    endmethod
    method read_u2 takes nothing returns integer
        return this.read_bits(2)
    endmethod
    method read_u3 takes nothing returns integer
        return this.read_bits(3)
    endmethod
    method read_u4 takes nothing returns integer
        return this.read_bits(4)
    endmethod
    method read_u5 takes nothing returns integer
        return this.read_bits(5)
    endmethod
    method read_u6 takes nothing returns integer
        return this.read_bits(6)
    endmethod
    method read_u7 takes nothing returns integer
        return this.read_bits(7)
    endmethod
    method read_u8 takes nothing returns integer
        return this.read_bits(8)
    endmethod
    method read_u9 takes nothing returns integer
        return this.read_bits(9)
    endmethod
    method read_u10 takes nothing returns integer
        return this.read_bits(10)
    endmethod
    method read_u11 takes nothing returns integer
        return this.read_bits(11)
    endmethod
    method read_u12 takes nothing returns integer
        return this.read_bits(12)
    endmethod
    method read_u13 takes nothing returns integer
        return this.read_bits(13)
    endmethod
    method read_u14 takes nothing returns integer
        return this.read_bits(14)
    endmethod
    method read_u15 takes nothing returns integer
        return this.read_bits(15)
    endmethod
    method read_u16 takes nothing returns integer
        return this.read_bits(16)
    endmethod
    method read_u17 takes nothing returns integer
        return this.read_bits(17)
    endmethod
    method read_u18 takes nothing returns integer
        return this.read_bits(18)
    endmethod
    method read_u19 takes nothing returns integer
        return this.read_bits(19)
    endmethod
    method read_u20 takes nothing returns integer
        return this.read_bits(20)
    endmethod
    method read_u21 takes nothing returns integer
        return this.read_bits(21)
    endmethod
    method read_u22 takes nothing returns integer
        return this.read_bits(22)
    endmethod
    method read_u23 takes nothing returns integer
        return this.read_bits(23)
    endmethod
    method read_u24 takes nothing returns integer
        return this.read_bits(24)
    endmethod
    method read_u25 takes nothing returns integer
        return this.read_bits(25)
    endmethod
    method read_u26 takes nothing returns integer
        return this.read_bits(26)
    endmethod
    method read_u27 takes nothing returns integer
        return this.read_bits(27)
    endmethod
    method read_u28 takes nothing returns integer
        return this.read_bits(28)
    endmethod
    method read_u29 takes nothing returns integer
        return this.read_bits(29)
    endmethod
    method read_u30 takes nothing returns integer
        return this.read_bits(30)
    endmethod
    method read_u31 takes nothing returns integer
        return this.read_bits(31)
    endmethod

    method read_s2 takes nothing returns integer
        return SHL(this.read_bits(2), 30) / pow2(30)
    endmethod
    method read_s3 takes nothing returns integer
        return SHL(this.read_bits(3), 29) / pow2(29)
    endmethod
    method read_s4 takes nothing returns integer
        return SHL(this.read_bits(4), 28) / pow2(28)
    endmethod
    method read_s5 takes nothing returns integer
        return SHL(this.read_bits(5), 27) / pow2(27)
    endmethod
    method read_s6 takes nothing returns integer
        return SHL(this.read_bits(6), 26) / pow2(26)
    endmethod
    method read_s7 takes nothing returns integer
        return SHL(this.read_bits(7), 25) / pow2(25)
    endmethod
    method read_s8 takes nothing returns integer
        return SHL(this.read_bits(8), 24) / pow2(24)
    endmethod
    method read_s9 takes nothing returns integer
        return SHL(this.read_bits(9), 23) / pow2(23)
    endmethod
    method read_s10 takes nothing returns integer
        return SHL(this.read_bits(10), 22) / pow2(22)
    endmethod
    method read_s11 takes nothing returns integer
        return SHL(this.read_bits(11), 21) / pow2(21)
    endmethod
    method read_s12 takes nothing returns integer
        return SHL(this.read_bits(12), 20) / pow2(20)
    endmethod
    method read_s13 takes nothing returns integer
        return SHL(this.read_bits(13), 19) / pow2(19)
    endmethod
    method read_s14 takes nothing returns integer
        return SHL(this.read_bits(14), 18) / pow2(18)
    endmethod
    method read_s15 takes nothing returns integer
        return SHL(this.read_bits(15), 17) / pow2(17)
    endmethod
    method read_s16 takes nothing returns integer
        return SHL(this.read_bits(16), 16) / pow2(16)
    endmethod
    method read_s17 takes nothing returns integer
        return SHL(this.read_bits(17), 15) / pow2(15)
    endmethod
    method read_s18 takes nothing returns integer
        return SHL(this.read_bits(18), 14) / pow2(14)
    endmethod
    method read_s19 takes nothing returns integer
        return SHL(this.read_bits(19), 13) / pow2(13)
    endmethod
    method read_s20 takes nothing returns integer
        return SHL(this.read_bits(20), 12) / pow2(12)
    endmethod
    method read_s21 takes nothing returns integer
        return SHL(this.read_bits(21), 11) / pow2(11)
    endmethod
    method read_s22 takes nothing returns integer
        return SHL(this.read_bits(22), 10) / pow2(10)
    endmethod
    method read_s23 takes nothing returns integer
        return SHL(this.read_bits(23), 9) / pow2(9)
    endmethod
    method read_s24 takes nothing returns integer
        return SHL(this.read_bits(24), 8) / pow2(8)
    endmethod
    method read_s25 takes nothing returns integer
        return SHL(this.read_bits(25), 7) / pow2(7)
    endmethod
    method read_s26 takes nothing returns integer
        return SHL(this.read_bits(26), 6) / pow2(6)
    endmethod
    method read_s27 takes nothing returns integer
        return SHL(this.read_bits(27), 5) / pow2(5)
    endmethod
    method read_s28 takes nothing returns integer
        return SHL(this.read_bits(28), 4) / pow2(4)
    endmethod
    method read_s29 takes nothing returns integer
        return SHL(this.read_bits(29), 3) / pow2(3)
    endmethod
    method read_s30 takes nothing returns integer
        return SHL(this.read_bits(30), 2) / pow2(2)
    endmethod
    method read_s31 takes nothing returns integer
        return SHL(this.read_bits(31), 1) / pow2(1)
    endmethod
    method read_s32 takes nothing returns integer
        // return SHL(this.read_bits(32), 0) / pow2(0)
        return this.read_bits(32)
    endmethod


static if ENABLE_REAL_READ_WRITE then

    method read_r16 takes nothing returns real
        return int_as_real(SHL(this.read_u16(), 16))
    endmethod

    method read_r32 takes nothing returns real
        return int_as_real(this.read_s32())
    endmethod

endif

    method read_bool takes nothing returns boolean
        return this.read_u1() == 0x1
    endmethod

    private static method b64_encode_table_init_8 takes string p1, string p2, string p3, string p4, string p5, string p6, string p7, string p8 returns nothing
        set b64_encode_table[b64_encode_index + 0] = p1
        set b64_encode_table[b64_encode_index + 1] = p2
        set b64_encode_table[b64_encode_index + 2] = p3
        set b64_encode_table[b64_encode_index + 3] = p4
        set b64_encode_table[b64_encode_index + 4] = p5
        set b64_encode_table[b64_encode_index + 5] = p6
        set b64_encode_table[b64_encode_index + 6] = p7
        set b64_encode_table[b64_encode_index + 7] = p8

        set b64_encode_index = b64_encode_index + 8
    endmethod
    private static method b64_encode_table_init takes nothing returns nothing
        call b64_encode_table_init_8("A", "B", "C", "D", "E", "F", "G", "H")
        call b64_encode_table_init_8("I", "J", "K", "L", "M", "N", "O", "P")
        call b64_encode_table_init_8("Q", "R", "S", "T", "U", "V", "W", "X")
        call b64_encode_table_init_8("Y", "Z", "a", "b", "c", "d", "e", "f")
        call b64_encode_table_init_8("g", "h", "i", "j", "k", "l", "m", "n")
        call b64_encode_table_init_8("o", "p", "q", "r", "s", "t", "u", "v")
        call b64_encode_table_init_8("w", "x", "y", "z", "0", "1", "2", "3")
        call b64_encode_table_init_8("4", "5", "6", "7", "8", "9", "+", "/")
    endmethod
    static method b64_from_int takes integer i returns string
        return b64_encode_table[i]
    endmethod

    private static method b64_decode_table_init takes nothing returns nothing
        local integer i

        set b64_decode_table = InitHashtable()

        set i = 0
        loop
            exitwhen i > 63

            // StringHash is case-insensitive, so there's no point in trying to store the lowercase letters
            if i <= 25 or i >= 52 then
                call SaveInteger(b64_decode_table, 0, StringHash(b64_from_int(i)), i)
            endif

            set i = i + 1
        endloop
    endmethod
    static method b64_to_int takes string b64 returns integer
        local integer i = LoadInteger(b64_decode_table, 0, StringHash(b64))
        if i <= 25 and b64 == StringCase(b64, /*lowercase*/false) then
            set i = i + 26
        endif
        return i
    endmethod

    private static method onInit takes nothing returns nothing
        call b64_encode_table_init()
        call b64_decode_table_init()
    endmethod


    method to_base64 takes nothing returns string
        local integer result_digits_count = round_up_nearest(this.bits_written, 6) / 6
        local string result = ""
        local integer i
        local integer u6
        local string b64

        call this.cache_state()

        call this.begin_reading()
        set i = 1
        loop
            exitwhen i > result_digits_count

            set u6 = this.read_u6()
            set b64 = b64_from_int(u6)
            set result = result + b64

            set i = i + 1
        endloop

        call this.restore_state_from_cache()

        return result
    endmethod

    static method base64_apply_colors takes string base64, string uc, string lc, string d, string p returns string
        local string result = ""
        local integer i
        local string c
        local integer o

        set i = 0
        loop
            set c = SubString(base64, i, i + 1)
            exitwhen c == ""
            set o = b64_to_int(c)

            set result = result + "|cff"

            if o <= 25 then
                set result = result + uc
            elseif o <= 51 then
                set result = result + lc
            elseif o <= 61 then
                set result = result + d
            else // if o <= 63 then
                set result = result + p
            endif

            set result = result + c + "|r"

            set i = i + 1
        endloop

        return result
    endmethod

    static method from_base64 takes string base64 returns thistype
        local thistype this = create(StringLength(base64) * 6)
        local integer i
        local string c
        local integer o

        set i = 0
        loop
            set c = SubString(base64, i, i + 1)
            exitwhen c == ""

            set o = b64_to_int(c)
            call this.write_u6(o)

            set i = i + 1
        endloop

        call this.begin_reading()

        return this
    endmethod


    // reference: https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm
    //
    static method luhn_generate_check_digit takes string base64 returns string
        local integer sum
        local integer factor
        local integer i
        local integer curr_digit
        local integer result_ordinal
        local string result

        set sum = 0
        set factor = 2
        set i = StringLength(base64) - 1
        loop
            exitwhen i < 0
            set curr_digit = b64_to_int(SubString(base64, i, i + 1))

            set curr_digit = curr_digit * factor
            if curr_digit >= 64 then
                set curr_digit = curr_digit - 63 // 63 = 64 - 1 (base = 64)
            endif

            set sum = sum + curr_digit

            if factor == 2 then
                set factor = 1
            else
                set factor = 2
            endif

            set i = i - 1
        endloop

        set result_ordinal = round_up_nearest(sum, 64) - sum
        set result = b64_from_int(result_ordinal)
        return result
    endmethod

    static method luhn_is_valid takes string base64 returns boolean
        local integer sum
        local integer factor
        local integer i
        local integer curr_digit

        set sum = 0
        set factor = 1
        set i = StringLength(base64) - 1
        loop
            exitwhen i < 0
            set curr_digit = b64_to_int(SubString(base64, i, i + 1))

            set curr_digit = curr_digit * factor
            if curr_digit >= 64 then
                set curr_digit = curr_digit - 63
            endif

            set sum = sum + curr_digit

            if factor == 1 then
                set factor = 2
            else
                set factor = 1
            endif

            set i = i - 1
        endloop

        return (sum - sum / 64 * 64) == 0
    endmethod
endstruct

endlibrary

Edit: the destroy method (which did nothing previously) is now disabled (panics when called) because deallocating the memory used by a BitBuf is kind of hard. =)
Edit2: the destroy method should work correctly now (apparently it wasn't that hard after all =))
 

Attachments

  • bit-buf-demo.w3x
    57 KB · Views: 91
  • bit-buf-demo-pic.png
    bit-buf-demo-pic.png
    841.7 KB · Views: 180
Last edited:

Deleted member 219079

D

Deleted member 219079

Nice idea.

Follow JPAG, use textmacros.
 
Level 13
Joined
Nov 7, 2014
Messages
571
What's the benefit of this?

Well in the demo map I tried to minimize the number of characters the player has to type in order to load his hero.

Of course one can try to minimize other things as well by packing bits into integers, for example the number of sync operations needed to sync the players camera positions.

(This is probably not a good example) I.e suppose we wanted to sync each player's camera target position to all the other players (let's limit the number of players to 3).
And we don't want to be supper precise:

JASS:
// requires: SyncInteger http://www.hiveworkshop.com/threads/syncinteger.278674/

// also requires a small modification to the BitBuf library, namely changing the .begin_readning method from private to public.
// otherwise one has to create another BitBuffer bb2 = BitBuf.from_base64(bb.to_base64()) and read from bb2

scope CameraPositionsSync initializer camera_positions_sync_init

function floor takes real r returns integer
    local integer i = R2I(r)

    if r == i then
        return i
    endif

    if r > 0 then
        return i
    else // if r < 0 then
        return i - 1
    endif
endfunction

globals
    constant real CAMERA_MIN_X = -768.0
    constant real CAMERA_MIN_Y = -1280.0
    constant real CAMERA_MAX_X = 768.0
    constant real CAMERA_MAX_Y = 768.0
    constant real GRID_RESOLUTION = 128.0
    // =>
    // the camera's dimensions are: 12x16
    //
    // where x's range is -6 .. 6
    // and y's range is -10 .. 6
    //
    // we can fit x into a s4 (s4's range is -8 .. 7)
    // we can fit y into a s5 (s5's range is -16 .. 15)
    // total bits: 9 = s4 + s5
    //

    // assume we have this many players
    constant integer PLAYERS_COUNT = 3

    real array player_camera_target_x
    real array player_camera_target_y
endglobals

globals
    integer rounded_player_camera_target_x_result
    integer rounded_player_camera_target_y_result
endglobals
function get_rounded_player_camera_target_xy takes integer p returns nothing
    set rounded_player_camera_target_x_result = 0
    set rounded_player_camera_target_y_result = 0
    if Player(p) == GetLocalPlayer() then
        set rounded_player_camera_target_x_result = floor(GetCameraTargetPositionX() / GRID_RESOLUTION)
        set rounded_player_camera_target_y_result = floor(GetCameraTargetPositionY() / GRID_RESOLUTION)
    endif
endfunction

function begin_sync_camera_positions takes nothing returns nothing
    local integer p
    local BitBuf bb = BitBuf.create(PLAYERS_COUNT * 9)
    local integer camera_positions

    set p = 0
    loop
        exitwhen p >= PLAYERS_COUNT
        call get_rounded_player_camera_target_xy(p)

        call bb.write_s4(rounded_player_camera_target_x_result)
        call bb.write_s5(rounded_player_camera_target_y_result)

        set p = p + 1
    endloop

    call bb.begin_reading()
    set camera_positions = bb.read_u27()
    call bb.destroy()

    call SyncInteger(/*from player:*/ 0, camera_positions)
    call SyncInteger(/*from player:*/ 1, camera_positions)
    call SyncInteger(/*from player:*/ 2, camera_positions)

    call BJDebugMsg("syncing camera positions...")
endfunction

function end_sync_camera_positions takes nothing returns nothing
    local integer sync_p = GetPlayerId(GetSyncedPlayer())
    local integer i = GetSyncedInteger()
    local BitBuf bb = BitBuf.create(PLAYERS_COUNT * 9)
    local integer p
    local integer x
    local integer y

    call bb.write_u27(i)
    call bb.begin_reading()

    set p = 0
    loop
        exitwhen p >= PLAYERS_COUNT

        set x = bb.read_s4()
        set y = bb.read_s5()

        if sync_p == p then
            set player_camera_target_x = x * GRID_RESOLUTION
            set player_camera_target_y = y * GRID_RESOLUTION
        endif

        set p = p + 1
    endloop

    call bb.destroy()

    call BJDebugMsg("...done syncing from player: " + I2S(sync_p))
endfunction

function camera_positions_sync_init takes nothing returns nothing
    call OnSyncInteger(function end_sync_camera_positions)
endfunction

endscope

We could of course decide to sync 6 reals instead, but that would be a bit more syncing.

PS: in my opinion the use of textmacros in this case would not result in any kind of improvement at all
 
Last edited:
Alright I see. I still don't know exactly how it works yet though.

But, I meant to say have you tested this against save systems to see how it compares in data compression? Or can this be used alongside current save systems?

Also, why don't you just do this?

JASS:
    method write takes integer u, integer bits_count returns nothing
        call this.write_bits(u, bits_count)
    endmethod
By the way my Sync library is much better at syncing data than SyncInteger.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Also, why don't you just do this?
JASS:
    method write takes integer u, integer bits_count returns nothing
        call this.write_bits(u, bits_count)
    endmethod

Well because the write_u|s[1..32] methods do range checks on their parameter
and that catches a lot of mistakes. I know I made quite a lot of them while doing the demo =).


But, I meant to say have you tested this against save systems to see how it compares in data compression? Or can this be used alongside current save systems?
No, I haven't.

By the way my Sync library is much better at syncing data than SyncInteger.
Well I saw that it has SyncInteger as a requirement and I thought why not use that directly instead?
 
Well because the write_u|s[1..32] methods do range checks on their parameter
and that catches a lot of mistakes.

I still don't understand, you can do this:

JASS:
    set ranges[0] = 0x1
    set ranges[1] = 0x3
    set ranges[2] = 0x7

    method write takes integer u, integer bits_count returns nothing
        debug if not (0x0 <= u and u <= ranges[bits_count]) then
        debug   call panic("write_u" + I2S(bits_count) + " range is 0 .. " + I2S(ranges[bits_count]) + "; value is " + I2S(u))
        debug endif
        call this.write_bits(u, bits_count)
    endmethod

Well I saw that it has SyncInteger as a requirement and I thought why not use that directly instead?

SyncInteger uses selection events to sync an integer value. If that value is higher than 9, or lower than 0, it increases the amount of selection events used.

Sync however uses Game Cache, and it will likely keep the value under 9 (probably won't have 9 instances of Sync simultaneously). It is much faster especially when syncing more than a value or two. It also supports real variables directly.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I still don't understand, you can do this:

JASS:
    set ranges[0] = 0x1
    set ranges[1] = 0x3
    set ranges[2] = 0x7

    method write takes integer u, integer bits_count returns nothing
        debug if not (0x0 <= u and u <= ranges[bits_count]) then
        debug   call panic("write_u" + I2S(bits_count) + " range is 0 .. " + I2S(ranges[bits_count]) + "; value is " + I2S(u))
        debug endif
        call this.write_bits(u, bits_count)
    endmethod

Example usage:
JASS:
call bb.write_u5(hero_id)
call bb.write_s7(camera_x)

vs

call bb.write_u(hero_id, 5)
call bb.write_s(camera_x, 7)

I've used the former because I find it more readable, it's a design decision I guess.

SyncInteger uses selection events to sync an integer value. If that value is higher than 9, or lower than 0, it increases the amount of selection events used.

Sync however uses Game Cache, and it will likely keep the value under 9 (probably won't have 9 instances of Sync simultaneously). It is much faster especially when syncing more than a value or two. It also supports real variables directly.
I still don't get why Sync requires SyncInteger... =)
 

Deleted member 219079

D

Deleted member 219079

SyncInteger is his older resource, which got approved. Sync is such a leap that deserves its own library, which is in submissions - forum.
 
I've used the former because I find it more readable, it's a design decision I guess.

I don't find it much more readable. In fact it makes the source code much harder to read, and bloats the war3map.j (increases filesize).

II still don't get why Sync requires SyncInteger... =)

Say you have 500 values to sync. In SyncInteger, that would be an event firing 500 times. In Sync it would synchronize all 500 values, while only firing the event once.

Sync uses Game Cache for the majority of the syncing process, but at the end it uses a SyncInteger call.
 
I find the demo somewhat hard to follow. From that point, I must admit, I very like TriggerHappy's save/load. Your style doesn't make it easier for me to understand, too.^^
Strings are a missing save/load feature, but else, I have not really something new to recommend, as you want to have it basicly for integers. It's welcome, too.

Personaly I find it easier to read:
0xFFFFFFFE ->-0x00000002 , etc.

The typo error with methd write_r32 does still exist in code. It was only fixed in the docu. (in map, too)

Could you make public functions part of the API, outsource them, or make them not public?

It works, and should be listed. When fixed the typo, and public functions, it can be approved I believe.

edit:

It can be approved after update.
 
Last edited:
Top