Add bit stream support
This commit is contained in:
parent
bd54850e38
commit
c423fdf983
@ -634,13 +634,15 @@ function GiveProgressiveDosh(NicePlayerController nicePlayer){
|
||||
}
|
||||
}
|
||||
simulated function Mutate(string MutateString, PlayerController kfPlayer){
|
||||
local int i;
|
||||
local int i, readLenght;
|
||||
local NicePlayerController nicePlayer;
|
||||
local NiceServerData remoteData;
|
||||
// Tokens from 'MutateString'
|
||||
local array<String> wordsArray;
|
||||
local String command, mod;
|
||||
local String white;
|
||||
local BitStreamWriter inputStream;
|
||||
local BitStreamReader outputStream;
|
||||
// Array with command modifiers.
|
||||
// Always contains at least 10 elements, that may be empty strings if there wasn't enough modifiers.
|
||||
// Done for safe access without the need to check for bounds.
|
||||
@ -732,8 +734,51 @@ simulated function Mutate(string MutateString, PlayerController kfPlayer){
|
||||
else if(command ~= "PRINT"){
|
||||
nicePlayer.ClientPrint();
|
||||
}
|
||||
else if(command ~= "TEST"){
|
||||
inputStream = new class'BitStreamWriter';
|
||||
outputStream = new class'BitStreamReader';
|
||||
inputStream.WriteInt(Len(mod), 5);
|
||||
inputStream.WriteClassName(mod);
|
||||
outputStream.Initialize(inputStream.GetData());
|
||||
readLenght = outputStream.ReadInt(5);
|
||||
nicePlayer.ClientMessage("Input lenght:" @ string(Len(mod)));
|
||||
nicePlayer.ClientMessage("Compressed lenght:" @ string(inputStream.GetSizeInBytes()) );
|
||||
nicePlayer.ClientMessage("Output:"@outputStream.ReadClassName(readLenght));
|
||||
}
|
||||
Super.Mutate(MutateString, kfPlayer);
|
||||
}
|
||||
/* Good test for writer
|
||||
else if(command ~= "TEST"){
|
||||
inputStream = new class'BitStreamWriter';
|
||||
outputStream = new class'BitStreamReader';
|
||||
//stream.PushByte(167, int(mod));
|
||||
inputStream.WriteInt(3, 3);
|
||||
inputStream.WriteByte(49, 7);
|
||||
inputStream.WriteInt(1651779982, 25);
|
||||
inputStream.WriteInt(2, 2);
|
||||
bytes = inputStream.GetData();
|
||||
bits = inputStream.GetSize();
|
||||
nicePlayer.ClientMessage("SIZE:" @ string(bits));
|
||||
for (i = 0; i < bytes.length; i += 1) {
|
||||
nicePlayer.ClientMessage("Content:" @ string(bytes[i]));
|
||||
}
|
||||
outputStream.Initialize(bytes);
|
||||
//nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(3)));
|
||||
//nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(7)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(8)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(4)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(4)));
|
||||
outputStream.Initialize(bytes);
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(3)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(7)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(5)));
|
||||
outputStream.Initialize(bytes);
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadInt(3)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadByte(7)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadInt(25)));
|
||||
nicePlayer.ClientMessage("OUT:" @ string(outputStream.ReadInt(2)));
|
||||
}
|
||||
*/
|
||||
// Event functions
|
||||
// Called at the start of the match
|
||||
function MatchBegan(){
|
||||
|
274
sources/_acedia/BitStreamReader.uc
Normal file
274
sources/_acedia/BitStreamReader.uc
Normal file
@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Class for packaging various data types into an array of bytes.
|
||||
* Copyright 2020 Anton Tarasenko
|
||||
*------------------------------------------------------------------------------
|
||||
* This file is part of Acedia.
|
||||
*
|
||||
* Acedia is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Acedia is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class BitStreamReader extends Object;
|
||||
|
||||
// Array of bytes to read from
|
||||
var private array<byte> stream;
|
||||
var private int currentBytePointer;
|
||||
var private int currentBitPointer;
|
||||
// Write `byte` from `stream[currentBytePointer]` to avoid unnecessary
|
||||
// array access calls.
|
||||
var private byte currentByte;
|
||||
|
||||
var private const array<byte> bitMask;
|
||||
var private const array<byte> bitMaskBefore;
|
||||
var private const array<byte> bitMaskAfter;
|
||||
|
||||
var private int tempInt;
|
||||
var private string tempString;
|
||||
var private byte temp;
|
||||
var private byte result;
|
||||
var private byte rightBoundary;
|
||||
var private byte remainingToRead;
|
||||
var private byte byte1, byte2, byte3, byte4;
|
||||
|
||||
public final function Initialize(array<byte> newStream)
|
||||
{
|
||||
stream = newStream;
|
||||
currentBytePointer = 0;
|
||||
currentBitPointer = 0;
|
||||
if (stream.length > 0) {
|
||||
currentByte = stream[0];
|
||||
}
|
||||
}
|
||||
|
||||
private final function ShiftBytePointer()
|
||||
{
|
||||
currentBitPointer = 0;
|
||||
currentBytePointer += 1;
|
||||
if (currentBytePointer < stream.length) {
|
||||
currentByte = stream[currentBytePointer];
|
||||
}
|
||||
else {
|
||||
currentByte = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public final function byte ReadBit()
|
||||
{
|
||||
if (currentBytePointer >= stream.length) return 0;
|
||||
if ((currentByte & bitMask[currentBitPointer]) > 0) {
|
||||
currentBitPointer += 1;
|
||||
if (currentBitPointer >= 8) {
|
||||
ShiftBytePointer();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
currentBitPointer += 1;
|
||||
if (currentBitPointer >= 8) {
|
||||
ShiftBytePointer();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public final function bool ReadBool()
|
||||
{
|
||||
if (currentBytePointer >= stream.length) return false;
|
||||
if ((currentByte & bitMask[currentBitPointer]) > 0) {
|
||||
currentBitPointer += 1;
|
||||
if (currentBitPointer >= 8) {
|
||||
ShiftBytePointer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
currentBitPointer += 1;
|
||||
if (currentBitPointer >= 8) {
|
||||
ShiftBytePointer();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final function byte ReadByte(optional int bitLimit)
|
||||
{
|
||||
if (currentBytePointer >= stream.length) return 0;
|
||||
// Default `bitLimit`
|
||||
if (bitLimit <= 0 || bitLimit > 8) {
|
||||
bitLimit = 8;
|
||||
}
|
||||
// Throw away already read part
|
||||
result = currentByte & bitMaskAfter[currentBitPointer];
|
||||
if (bitLimit <= 8 - currentBitPointer)
|
||||
{
|
||||
// If all we are asked to read is contained in cuurent `byte` -
|
||||
// just throw away excessive info by shifting to the right
|
||||
result = result >>> (8 - currentBitPointer - bitLimit);
|
||||
currentBitPointer += bitLimit;
|
||||
if (currentBitPointer >= 8) {
|
||||
ShiftBytePointer();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// Otherwise shift already read part up
|
||||
rightBoundary = bitLimit - (8 - currentBitPointer);
|
||||
result = result << rightBoundary;
|
||||
// Move to the next byte
|
||||
ShiftBytePointer();
|
||||
currentBitPointer = rightBoundary;
|
||||
// Record required part from the second byte
|
||||
temp = currentByte & bitMaskBefore[rightBoundary];
|
||||
temp = temp >>> (8 - rightBoundary);
|
||||
result = result | temp;
|
||||
return result;
|
||||
}
|
||||
|
||||
public final function int ReadInt(optional int bitLimit)
|
||||
{
|
||||
// Default `bitLimit`
|
||||
if (bitLimit <= 0 || bitLimit > 32) {
|
||||
bitLimit = 32;
|
||||
}
|
||||
byte1 = ReadByte(bitLimit);
|
||||
if (bitLimit <= 8) {
|
||||
return byte1;
|
||||
}
|
||||
bitLimit -= 8;
|
||||
byte2 = ReadByte(bitLimit);
|
||||
if (bitLimit <= 8)
|
||||
{
|
||||
tempInt = byte1 << bitLimit;
|
||||
tempInt = tempInt | byte2;
|
||||
return tempInt;
|
||||
}
|
||||
bitLimit -= 8;
|
||||
byte3 = ReadByte(bitLimit);
|
||||
if (bitLimit <= 8)
|
||||
{
|
||||
tempInt = byte1 << (bitLimit + 8);
|
||||
tempInt = tempInt | (byte2 << bitLimit);
|
||||
tempInt = tempInt | byte3;
|
||||
return tempInt;
|
||||
}
|
||||
bitLimit -= 8;
|
||||
byte4 = ReadByte(bitLimit);
|
||||
tempInt = byte1 << (bitLimit + 16);
|
||||
tempInt = tempInt | (byte2 << (bitLimit + 8));
|
||||
tempInt = tempInt | (byte3 << bitLimit);
|
||||
tempInt = tempInt | byte4;
|
||||
return tempInt;
|
||||
}
|
||||
|
||||
public final function float ReadFloat(
|
||||
int precisionLevel,
|
||||
optional byte bitLimit)
|
||||
{
|
||||
if (bitLimit < 0 || bitLimit > 32) {
|
||||
bitLimit = 32;
|
||||
}
|
||||
precisionLevel = Max(0, precisionLevel);
|
||||
return float(ReadInt(bitLimit)) * (0.1 ** precisionLevel);
|
||||
}
|
||||
|
||||
public final function string ReadString(int length)
|
||||
{
|
||||
if (length <= 0) return "";
|
||||
while (length > 0)
|
||||
{
|
||||
length -= 1;
|
||||
tempString $= Chr(ReadByte());
|
||||
}
|
||||
return tempString;
|
||||
}
|
||||
|
||||
// String representation of `class`es has a more limited character range,
|
||||
// which allows us to fit every chgaracter in `class`' name into 6 bits,
|
||||
// saving 25% space.
|
||||
// Allowed characters are: digits, upper and lower case letters, dot '.'
|
||||
// and underscore '_'.
|
||||
// Any other symbol is converted into an underscore.
|
||||
private final function byte CompressClassCharacter(byte source)
|
||||
{
|
||||
// 26 upper case character
|
||||
if (source >= 65 && source <= 90) {
|
||||
return source - 65;
|
||||
}
|
||||
// 26 lower case character // 52 total
|
||||
if (source >= 97 && source <= 122) {
|
||||
return 26 + (source - 97);
|
||||
}
|
||||
// 10 digits // 62 total
|
||||
if (source >= 48 && source <= 57) {
|
||||
return 52 + (source - 48);
|
||||
}
|
||||
// dot
|
||||
if (source == 46) {
|
||||
return 62;
|
||||
}
|
||||
// underscore and everything else
|
||||
return 63;
|
||||
}
|
||||
|
||||
private final function byte DecompressClassCharacter(byte source)
|
||||
{
|
||||
if (source >= 63) return 95;
|
||||
// 26 upper case character
|
||||
if (source <= 25) {
|
||||
return source + 65;
|
||||
}
|
||||
// 26 lower case character
|
||||
if (source <= 51) {
|
||||
return source + 71;
|
||||
}
|
||||
// 10 digits
|
||||
if (source <= 61) {
|
||||
return source - 4;
|
||||
}
|
||||
// dot
|
||||
// if (source == 62)
|
||||
return 46;
|
||||
}
|
||||
|
||||
public final function string ReadClassName(int length)
|
||||
{
|
||||
if (length <= 0) return "";
|
||||
while (length > 0)
|
||||
{
|
||||
length -= 1;
|
||||
tempString $= Chr(DecompressClassCharacter(ReadByte(6)));
|
||||
}
|
||||
return tempString;
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
bitMask(0) = 128 // 1 0 0 0 0 0 0 0
|
||||
bitMask(1) = 64 // 0 1 0 0 0 0 0 0
|
||||
bitMask(2) = 32 // 0 0 1 0 0 0 0 0
|
||||
bitMask(3) = 16 // 0 0 0 1 0 0 0 0
|
||||
bitMask(4) = 8 // 0 0 0 0 1 0 0 0
|
||||
bitMask(5) = 4 // 0 0 0 0 0 1 0 0
|
||||
bitMask(6) = 2 // 0 0 0 0 0 0 1 0
|
||||
bitMask(7) = 1 // 0 0 0 0 0 0 0 1
|
||||
bitMaskBefore(0) = 128 // 1 0 0 0 0 0 0 0
|
||||
bitMaskBefore(1) = 192 // 1 1 0 0 0 0 0 0
|
||||
bitMaskBefore(2) = 224 // 1 1 1 0 0 0 0 0
|
||||
bitMaskBefore(3) = 240 // 1 1 1 1 0 0 0 0
|
||||
bitMaskBefore(4) = 248 // 1 1 1 1 1 0 0 0
|
||||
bitMaskBefore(5) = 252 // 1 1 1 1 1 1 0 0
|
||||
bitMaskBefore(6) = 254 // 1 1 1 1 1 1 1 0
|
||||
bitMaskBefore(7) = 255 // 1 1 1 1 1 1 1 1
|
||||
bitMaskAfter(0) = 255 // 1 1 1 1 1 1 1 1
|
||||
bitMaskAfter(1) = 127 // 0 1 1 1 1 1 1 1
|
||||
bitMaskAfter(2) = 63 // 0 0 1 1 1 1 1 1
|
||||
bitMaskAfter(3) = 31 // 0 0 0 1 1 1 1 1
|
||||
bitMaskAfter(4) = 15 // 0 0 0 0 1 1 1 1
|
||||
bitMaskAfter(5) = 7 // 0 0 0 0 0 1 1 1
|
||||
bitMaskAfter(6) = 3 // 0 0 0 0 0 0 1 1
|
||||
bitMaskAfter(7) = 1 // 0 0 0 0 0 0 0 1
|
||||
}
|
352
sources/_acedia/BitStreamWriter.uc
Normal file
352
sources/_acedia/BitStreamWriter.uc
Normal file
@ -0,0 +1,352 @@
|
||||
/**
|
||||
* Class for packaging various data types into an array of bytes.
|
||||
* Copyright 2020 Anton Tarasenko
|
||||
*------------------------------------------------------------------------------
|
||||
* This file is part of Acedia.
|
||||
*
|
||||
* Acedia is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Acedia is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class BitStreamWriter extends Object;
|
||||
|
||||
// Array of fully written bytes
|
||||
var private array<byte> stream;
|
||||
// Last byte that still has some space to fit data into.
|
||||
// We start writing data into it's least significant bits and shift them up
|
||||
// when we want to write more data inside or when we are asked to return
|
||||
// recorded data.
|
||||
var private byte unfinishedByte;
|
||||
// How much space is left in `unfinishedByte`
|
||||
var private byte bitsLeft;
|
||||
|
||||
// Can be used to erase unnecessary bits from a byte
|
||||
var private const array<byte> bitMaskAfter;
|
||||
|
||||
// We'll declare all auxiliary variables as globals to avoid their
|
||||
// unnecessary creation inside functions, to make them more lightweight.
|
||||
var private byte shift, altShift;
|
||||
var private byte temp;
|
||||
var private int tempInt;
|
||||
var private byte byte1, byte2, byte3, byte4;
|
||||
|
||||
/**
|
||||
* Resets contents of the `BitStreamWriter`, making it the same as a
|
||||
* brand new writer.
|
||||
*
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter InitializeStream()
|
||||
{
|
||||
stream.length = 0;
|
||||
unfinishedByte = 0;
|
||||
bitsLeft = 8;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a bit inside a `BitStreamWriter`.
|
||||
*
|
||||
* @param source Byte that defines a bit. We don't look at any actual bits in
|
||||
* `source`'s representation, but simply consired the bit to be
|
||||
* `1` if `source > 0` and `0` if `source == 0`.
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter WriteBit(byte source)
|
||||
{
|
||||
unfinishedByte = unfinishedByte << 1;
|
||||
if (source > 0) {
|
||||
unfinishedByte += 1;
|
||||
}
|
||||
bitsLeft -= 1;
|
||||
if (bitsLeft <= 0) {
|
||||
stream[stream.length] = unfinishedByte;
|
||||
unfinishedByte = 0;
|
||||
bitsLeft = 8;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a bit inside a `BitStreamWriter`.
|
||||
*
|
||||
* @param source Boolean value that defines a bit. `true` means bit is equal
|
||||
* to `1` and `false` means that it is equal to `0`.
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter WriteBoolean(bool isOne)
|
||||
{
|
||||
unfinishedByte = unfinishedByte << 1;
|
||||
if (isOne) {
|
||||
unfinishedByte += 1;
|
||||
}
|
||||
// Update storage
|
||||
bitsLeft -= 1;
|
||||
if (bitsLeft <= 0) {
|
||||
stream[stream.length] = unfinishedByte;
|
||||
unfinishedByte = 0;
|
||||
bitsLeft = 8;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a `byte` inside a `BitStreamWriter`.
|
||||
*
|
||||
* @param source Boolean value that defines a bit. `true` means bit is
|
||||
* equal to `1` and `false` means that it is equal to `0`.
|
||||
* @param bitLimit How many of the (least significant) bits to record;
|
||||
* can be used to compress values that don't need the full `byte`
|
||||
* value range.
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter WriteByte(
|
||||
byte source,
|
||||
optional byte bitLimit)
|
||||
{
|
||||
// Default `bitLimit`
|
||||
if (bitLimit < 0 || bitLimit > 8) {
|
||||
bitLimit = 8;
|
||||
}
|
||||
// Zero unnecessary bits
|
||||
source = source & bitMaskAfter[8 - bitLimit];
|
||||
if (bitLimit < bitsLeft)
|
||||
{
|
||||
// We have enough space to fit all the bits in `unfinishedByte`
|
||||
unfinishedByte = unfinishedByte << bitLimit;
|
||||
unfinishedByte = unfinishedByte | source;
|
||||
// Update storage
|
||||
bitsLeft -= bitLimit;
|
||||
if (bitsLeft <= 0)
|
||||
{
|
||||
stream[stream.length] = unfinishedByte;
|
||||
unfinishedByte = 0;
|
||||
bitsLeft = 8;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
// If we don't have enough space, - record what can fit in
|
||||
// current `unfinishedByte`
|
||||
unfinishedByte = unfinishedByte << bitsLeft;
|
||||
altShift = bitLimit - bitsLeft;
|
||||
temp = source >>> altShift;
|
||||
unfinishedByte = unfinishedByte | temp;
|
||||
// And add it to the storage
|
||||
stream[stream.length] = unfinishedByte;
|
||||
// Create new byte by erasing recorded bits from the `source`
|
||||
temp = temp << altShift;
|
||||
unfinishedByte = source ^ temp;
|
||||
bitsLeft = 8 - altShift;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an `int` inside a `BitStreamWriter`.
|
||||
*
|
||||
* @param source Integer value to record into `BitStreamWriter`.
|
||||
* @param bitLimit How many of the (least significant) bits to record;
|
||||
* can be used to compress values that don't need the full `int`
|
||||
* value range.
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter WriteInt(
|
||||
int source,
|
||||
optional byte bitLimit)
|
||||
{
|
||||
// Default `bitLimit`
|
||||
if (bitLimit == 0) {
|
||||
bitLimit = 32;
|
||||
}
|
||||
byte1 = byte((source & 0xff000000) >>> 24);
|
||||
byte2 = byte((source & 0x00ff0000) >>> 16);
|
||||
byte3 = byte((source & 0x0000ff00) >>> 8);
|
||||
byte4 = source & 0x000000ff;
|
||||
if (bitLimit > 24) {
|
||||
WriteByte(byte1, bitLimit - 24);
|
||||
}
|
||||
if (bitLimit > 16) {
|
||||
WriteByte(byte2, bitLimit - 16);
|
||||
}
|
||||
if (bitLimit > 8) {
|
||||
WriteByte(byte3, bitLimit - 8);
|
||||
}
|
||||
WriteByte(byte4, bitLimit);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an `float` inside a `BitStreamWriter`.
|
||||
*
|
||||
* `floats` can only be recorded with a specified amount of decimal places.
|
||||
* This is because we can't really get precise float bit representation or
|
||||
* easily truncate it's value bit-wise, so we multiply it by a specified
|
||||
* power of 10 and then transfer integer part of the result as `int`.
|
||||
* Specified precision level IS NOT recorded into `BitStreamWriter`.
|
||||
*
|
||||
* @param source Integer value to record into `BitStreamWriter`.
|
||||
* @param precisionLevel How many decimal places after the dot to record.
|
||||
* @param bitLimit How many of the (least significant) bits to record;
|
||||
* can be used to compress values that don't need the full value range of
|
||||
* truncated float.
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter WriteFloat(
|
||||
float source,
|
||||
int precisionLevel,
|
||||
optional byte bitLimit)
|
||||
{
|
||||
if (bitLimit < 0 || bitLimit > 32) {
|
||||
bitLimit = 32;
|
||||
}
|
||||
precisionLevel = Max(0, precisionLevel);
|
||||
tempInt = int(Round( source * (10 ** precisionLevel) ));
|
||||
WriteInt(tempInt, bitLimit);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an `string` inside a `BitStreamWriter`, treating each code point as
|
||||
* a byte, which will lead to loss of data when recording `string`s that
|
||||
* contain code points with values `> 255`.
|
||||
*
|
||||
* Does not record `string`'s length or where it ends.
|
||||
*
|
||||
* @param source `string` value to record into `BitStreamWriter`.
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter WriteString(string source)
|
||||
{
|
||||
while (source != "")
|
||||
{
|
||||
WriteByte(Asc(source));
|
||||
source = Mid(0, 1);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an string representation of a `class` name inside
|
||||
* a `BitStreamWriter`, compressing it's characters to 6 bit representation.
|
||||
*
|
||||
* Allowed characters are: digits, upper and lower case letters, dot '.'
|
||||
* and underscore '_'.
|
||||
* Any other character will be converted into underscore.
|
||||
*
|
||||
* Does not record `string`'s length or where it ends.
|
||||
*
|
||||
* @param source String representation of a `class`' name value to record
|
||||
* into `BitStreamWriter`.
|
||||
* @return `BitStreamWriter` to allow for function chaining.
|
||||
*/
|
||||
public final function BitStreamWriter WriteClassName(string source)
|
||||
{
|
||||
while (source != "")
|
||||
{
|
||||
WriteByte(CompressClassCharacter(Asc(source)), 6);
|
||||
source = Mid(source, 1);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// String representation of `class`es has a more limited character range,
|
||||
// which allows us to fit every chgaracter in `class`' name into 6 bits,
|
||||
// saving 25% space.
|
||||
// Allowed characters are: digits, upper and lower case letters, dot '.'
|
||||
// and underscore '_'.
|
||||
// Any other symbol is converted into an underscore.
|
||||
private final function byte CompressClassCharacter(byte source)
|
||||
{
|
||||
// 26 upper case character
|
||||
if (source >= 65 && source <= 90) {
|
||||
return source - 65;
|
||||
}
|
||||
// 26 lower case character // 52 total
|
||||
if (source >= 97 && source <= 122) {
|
||||
return 26 + (source - 97);
|
||||
}
|
||||
// 10 digits // 62 total
|
||||
if (source >= 48 && source <= 57) {
|
||||
return 52 + (source - 48);
|
||||
}
|
||||
// dot
|
||||
if (source == 46) {
|
||||
return 62;
|
||||
}
|
||||
// underscore (`95`) and everything else
|
||||
return 63;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all data recorded so far in a caller `BitStreamWriter` as
|
||||
* an array of bytes. Data is written ion order, starting from bytes with
|
||||
* lesser indecies and their most significant bits.
|
||||
*
|
||||
* @return Array of bytes that contains all data written into caller
|
||||
* `BitStreamWriter`.
|
||||
*/
|
||||
public final function array<byte> GetData()
|
||||
{
|
||||
local array<byte> result;
|
||||
result = stream;
|
||||
if (bitsLeft < 8) {
|
||||
result[result.length] = unfinishedByte << bitsLeft;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns amount of data (in bits) recorded into caller `BitStreamWriter`.
|
||||
*
|
||||
* @return Amount (in bits) of recorded data.
|
||||
*/
|
||||
public final function int GetSize()
|
||||
{
|
||||
local int sizeInBits;
|
||||
sizeInBits = stream.length * 8;
|
||||
if (bitsLeft < 8) {
|
||||
sizeInBits += (8 - bitsLeft);
|
||||
}
|
||||
return sizeInBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns amount of data (in bytes) recorded into caller `BitStreamWriter`.
|
||||
*
|
||||
* @param onlyFullBytes Only count filled bytes, i.e. if last byte only has
|
||||
* 5 bits (or any amount `<8`) of info written in it -
|
||||
* method will not count it.
|
||||
* @return Amount (in bytes) of recorded data.
|
||||
*/
|
||||
public final function int GetSizeInBytes(optional bool onlyFullBytes)
|
||||
{
|
||||
local int sizeInBytes;
|
||||
sizeInBytes = stream.length;
|
||||
if (!onlyFullBytes && bitsLeft < 8) {
|
||||
sizeInBytes += 1;
|
||||
}
|
||||
return sizeInBytes;
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
// With this we do not need to call `Initialize` to use a newly created
|
||||
// `BitStreamWriter`
|
||||
bitsLeft = 8
|
||||
bitMaskAfter(0) = 255 // 1 1 1 1 1 1 1 1
|
||||
bitMaskAfter(1) = 127 // 0 1 1 1 1 1 1 1
|
||||
bitMaskAfter(2) = 63 // 0 0 1 1 1 1 1 1
|
||||
bitMaskAfter(3) = 31 // 0 0 0 1 1 1 1 1
|
||||
bitMaskAfter(4) = 15 // 0 0 0 0 1 1 1 1
|
||||
bitMaskAfter(5) = 7 // 0 0 0 0 0 1 1 1
|
||||
bitMaskAfter(6) = 3 // 0 0 0 0 0 0 1 1
|
||||
bitMaskAfter(7) = 1 // 0 0 0 0 0 0 0 1
|
||||
}
|
Loading…
Reference in New Issue
Block a user