So I am growing frustrated with my save game CSV structure, and found a way to convert over to binary and use memblocks to directly load integers and floats without converting from strings. My save game data is only ~15% strings, and a save can be 1000 tokens in size.
This working example requires you to write and read back the data in the exact same order to take advantage of the built-in indexing. Integers and floats get 4 bytes. When inserting strings first an integer with the ByteLen() + 1 gets added followed by the string, this is all done in the background and keeps track of the offset as you read and write. The memory block size is maintained as a multiple of 4.
I have yet to implement into Automation, this was the first step as a proof of concept. I will post back with loading times but it won't be a direct comparison as I plan on re-doing the way the level gets generated after loading the data. It can't get any worse than reading back from strings, can it?
Anyway if this concept is of use to anyone out here it is a cleaned up copy pasted below.
JD
/*
* Memory Block Save Game Data Example
*
* May 2022 One Tree Hill Studios
*
* - Build (write) and Read order must match for the offset to work
* - Build the memory block with filemb_addInteger(), filemb_addFloat(), and filemb_addString()
* - Read back the memory block with filemb_getInteger(), filemb_getFloat(), and filemb_getString()
* - Uses custom data type filemb_dataValueType to keep track of the offset while writing/reading
* - Write and read directly to disk with CreateFileFromMemblock() and CreateMemblockFromFile()
*/
SetErrorMode(2)
// set window properties
SetWindowTitle( "memblock_fileSave" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window
// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 30, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 )
SetPrintSize(16)
type filemb_dataValueType //This is a required type for the memory block build/read functions
mb as integer //Memory Block
offset as integer //Offset that gets automatically incremented
inter as integer //Byte size 4
floater as float //Byte size 4
stringer as string //Byte size = ByteLen() + 1
endtype
/*
* This variable will be used to build and read the memory block.
*/
global filemb_dataValue as filemb_dataValueType
/*
* The following type and variables are for the working demo
* (the game data would match your save game stucture)
*/
type gameDataType
screen as integer
name as string
date as string
time as string
version as float
endtype
global gameData as gameDataType //Randomly generated game data placed here
global displayDataTxt as integer //Display text for the generated game data
global displayMBTxt as integer //Display text for reading back the memblock data
controlsLoadBtn() //Load the control buttons (text buttons)
gameDataLoad() //Loads randomly generated game data to gameData (gameDataType)
displayGeneratedData() //Display the randomly generated game data
do
Print( ScreenFPS() )
controlsChkBtn() //Control (text) button controls
Sync()
loop
/*
* This function is an example of how to build up the memory block, the offset is automatically incremented.
*
* All integers and floats use 4 bytes, strings are dynamic and will automatically add a
* preceeding integer with the ByteLen() + 1 of the string
*/
function mb_buildData()
if (GetMemblockExists(filemb_dataValue.mb) = 1) then DeleteMemblock(filemb_dataValue.mb)
filemb_dataValue.mb = CreateMemblock(0) //Memory block sizes are auto incremented as data is added
filemb_dataValue.offset = 0 //Remember to reset this to zero when reading back
filemb_dataValue = filemb_addInteger(filemb_dataValue, gameData.screen)
filemb_dataValue = filemb_addString(filemb_dataValue, gameData.name)
filemb_dataValue = filemb_addString(filemb_dataValue, gameData.date)
filemb_dataValue = filemb_addString(filemb_dataValue, gameData.time)
filemb_dataValue = filemb_addFloat(filemb_dataValue, gameData.version)
endfunction
/*
* This function is an exmaple of how to read back data from the memory block.
*
* Like the add functions, the offset is automatically incremented.
*/
function mb_readData()
gameData.screen = -1
gameData.name = ""
gameData.date = ""
gameData.time = ""
gameData.version = 0.0
filemb_dataValue.offset = 0 //Reset offset to zero for reading back values
//Read the data back in the exact same order as it was created
gameData.screen = filemb_getInteger(filemb_dataValue)
gameData.name = filemb_getString(filemb_dataValue)
gameData.date = filemb_getString(filemb_dataValue)
gameData.time = filemb_getString(filemb_dataValue)
gameData.version = filemb_getFloat(filemb_dataValue)
endfunction
/*
* WORKING EXAMPLE FUNCTIONS
*
*/
//Randomly generat some game data
function gameDataLoad()
gameData.screen = Random(1, 200)
gameData.name = "scrn_" + str(Random(1, 2000))
gameData.date = GetCurrentDate()
gameData.time = GetCurrentTime()
gameData.version = Random(100, 999) / 10.0
endfunction
//Load the text button controls
function controlsLoadBtn()
global btn_loadRandom as integer = 0
btn_loadRandom = CreateText("Generate Random Game Data")
SetTextSize(btn_loadRandom, 20)
SetTextColor(btn_loadRandom, 255, 255, 255, 255)
SetTextAlignment(btn_loadRandom, 2)
SetTextPosition(btn_loadRandom, 260, 50)
global btn_buildMB as integer = 0
btn_buildMB = CreateText("Build Memory Block")
SetTextSize(btn_buildMB, 20)
SetTextColor(btn_buildMB, 255, 255, 255, 255)
SetTextAlignment(btn_buildMB, 2)
SetTextPosition(btn_buildMB, 260, 300)
endfunction
function controlsChkBtn()
//Button control for generating random game dat
if (GetTextHitTest(btn_loadRandom, GetPointerX(), GetPointerY()) = 1)
SetTextColor(btn_loadRandom, 0, 255, 0, 255)
if (GetPointerPressed() = 1)
gameDataLoad()
displayGeneratedData()
endif
else
SetTextColor(btn_loadRandom, 255, 255, 255, 255)
endif
//Button control for generating memblock and reading back data
if (GetTextHitTest(btn_buildMB, GetPointerX(), GetPointerY()) = 1)
SetTextColor(btn_buildMB, 0, 255, 0, 255)
if (GetPointerPressed() = 1)
mb_buildData()
mb_readData()
displayBuiltMB()
endif
else
SetTextColor(btn_buildMB, 255, 255, 255, 255)
endif
endfunction
//Displays the randomly generated game data
function displayGeneratedData()
dispDataTxt as string = ""
dispDataTxt = "Generated Game Data:" + chr(10)
dispDataTxt = dispDataTxt + "gameData.screen: " + str(gameData.screen) + chr(10)
dispDataTxt = dispDataTxt + "gameData.name: " + gameData.name + chr(10)
dispDataTxt = dispDataTxt + "gameData.date: " + gameData.date + chr(10)
dispDataTxt = dispDataTxt + "gameData.time: " + gameData.time + chr(10)
dispDataTxt = dispDataTxt + "gameData.version: " + str(gameData.version) + chr(10)
if (GetTextExists(displayDataTxt) = 1) then DeleteText(displayDataTxt)
displayDataTxt = CreateText(dispDataTxt)
SetTextSize(displayDataTxt, 20)
SetTextColor(displayDataTxt, 255, 255, 255, 255)
SetTextAlignment(displayDataTxt, 0)
SetTextPosition(displayDataTxt, 300, 25)
endfunction
//Displays the game data read back from memory block
function displayBuiltMB()
dispMBTxt as string = ""
dispMBTxt = dispMBTxt + "Built Memory Block:" + chr(10)
dispMBTxt = dispMBTxt + "Size = " + str(GetMemblockSize(filemb_dataValue.mb)) + chr(10)
dispMBTxt = dispMBTxt + "Screen = " + str(gameData.screen) + chr(10)
dispMBTxt = dispMBTxt + "Name = " + gameData.name + chr(10)
dispMBTxt = dispMBTxt + "Date = " + gameData.date + chr(10)
dispMBTxt = dispMBTxt + "Time = " + gameData.time + chr(10)
dispMBTxt = dispMBTxt + "Version = " + str(gameData.version) + chr(10)
if (GetTextExists(displayMBTxt) = 1) then DeleteText(displayMBTxt)
displayMBTxt = CreateText(dispMBTxt)
SetTextSize(displayMBTxt, 20)
SetTextColor(displayMBTxt, 255, 255, 255, 255)
SetTextAlignment(displayMBTxt, 0)
SetTextPosition(displayMBTxt, 300, 250)
endfunction
/*
* MAIN MEMEORY BLOCK FUNCTIONS
*
* Memory Block Game Save
* May 2022 by John Douglas
* www.onetreehillstudios.com
*
*/
//Writes an integer to a memory block
function filemb_addInteger(getMBint as filemb_dataValueType, getData as integer)
makemb_tempMB_int as integer = 0
makemb_tempMB_int = CreateMemblock(GetMemblockSize(getMBint.mb) + 4) //Create temporary memory block with size for integer
CopyMemblock(getMBint.mb, makemb_tempMB_int, 0, 0, GetMemblockSize(getMBint.mb))
SetMemblockInt(makemb_tempMB_int, getMBint.offset, getData) //Set integer data
getMBint.offset = getMBint.offset + 4 //Increment offset for filemb_dataValueType
if (GetMemblockExists(getMBint.mb) = 1) then DeleteMemblock(getMBint.mb)
getMBint.mb = makemb_tempMB_int //Copy temporary memory block over to filemb_dataValueType
endfunction getMBint
//Writes a float to a memory block
function filemb_addFloat(getMBfloat as filemb_dataValueType, getData as float)
makemb_tempMB as integer = 0
makemb_tempMB = CreateMemblock(GetMemblockSize(getMBfloat.mb) + 4)
CopyMemblock(getMBfloat.mb, makemb_tempMB, 0, 0, GetMemblockSize(getMBfloat.mb))
SetMemblockFloat(makemb_tempMB, getMBfloat.offset, getData) //Set float data
getMBfloat.offset = getMBfloat.offset + 4 //Increment offset for filemb_dataValueType
if (GetMemblockExists(getMBfloat.mb) = 1) then DeleteMemblock(getMBfloat.mb)
getMBfloat.mb = makemb_tempMB //Copy temporary memory block over to filemb_dataValueType
endfunction getMBfloat
//Writes a string to a memory block including the preceeding integer containing length of string
function filemb_addString(getMBstr as filemb_dataValueType, getDataString as string)
makemb_len as integer = 0 //Each string is preceeded by its length as an integer
makemb_len = ByteLen(getDataString) + 1 //Length of string plus 1 for end return character
getMBstr = filemb_addInteger(getMBstr, makemb_len) //Add string length (this will automatically increment the offset)
makemb_tempMB_str as integer = 0
makemb_tempMB_str = CreateMemblock(GetMemblockSize(getMBstr.mb) + makemb_len)
CopyMemblock(getMBstr.mb, makemb_tempMB_str, 0, 0, GetMemblockSize(getMBstr.mb))
SetMemblockString(makemb_tempMB_str, getMBstr.offset, getDataString) //Set string data
getMBstr.offset = getMBstr.offset + makemb_len //Increment offset for filemb_dataValueType based on string size
if (GetMemblockExists(getMBstr.mb) = 1) then DeleteMemblock(getMBstr.mb)
getMBstr.mb = makemb_tempMB_str //Copy temporary memory block over to filemb_dataValueType
getMBstr = checkDataMod4(getMBstr) //Check that the offset and memblock size are a multiple of 4
endfunction getMBstr
//Reads an integer from a memory block
function filemb_getInteger(getData ref as filemb_dataValueType) //Save Game Read Memblock - Returns Integer value
filemb_dataValueTypeClear(getData)
readmb_checkMBsize(getData, 4)
getData.inter = GetMemblockInt(getData.mb, getData.offset)
getData.offset = getData.offset + 4 //Byte offset for integer size 4
endfunction getData.inter
//Reads a float from a memory block
function filemb_getFloat(getData ref as filemb_dataValueType) //Save Game Read Memblock - Returns Float value
filemb_dataValueTypeClear(getData)
readmb_checkMBsize(getData, 4)
getData.floater = GetMemblockFloat(getData.mb, getData.offset)
getData.offset = getData.offset + 4 //Byte offset for float size 4
endfunction getData.floater
//Reads a string from a memory block including the preceeding integer containing length of string
function filemb_getString(getData ref as filemb_dataValueType) //Save Game Read Memblock - Returns String value
filemb_dataValueTypeClear(getData)
readmb_checkMBsize(getData, 4)
readmb_stringLen as integer = 0 //Temporary value for length of string to return
readmb_stringLen = GetMemblockInt(getData.mb, getData.offset) //Get length of string
getData.offset = getData.offset + 4 //Byte offset for string length value (integer)
readmb_checkMBsize(getData, readmb_stringLen)
if (readmb_stringLen <= 0)
Message("Found Empty String" + chr(10) + "getData.mb = " + str(getData.mb) + chr(10) + "readmb_stringLen = " + str(readmb_stringLen) + chr(10) + "getData.offset = " + str(getData.offset))
endif
getData.stringer = GetMemblockString(getData.mb, getData.offset, readmb_stringLen)
getData.offset = getData.offset + readmb_stringLen //Byte offset for string (based on string length)
if (Mod(getData.offset, 4) <> 0) //Offsets need to be a multiple of 4, check for remainder here
while (Mod(getData.offset, 4) <> 0)
getData.offset = getData.offset + 1 //Keep adding 1 until multiple of 4
endwhile
endif
endfunction getData.stringer
//Clears the generic data type used to retrieve save game
function filemb_dataValueTypeClear(getData ref as filemb_dataValueType)
getData.inter = 0
getData.floater = 0.0
getData.stringer = ""
endfunction
//Checks if MB is big enough to accomodate offset
function readmb_checkMBsize(getData ref as filemb_dataValueType, getSize as integer)
readmb_size_mb as integer
if (GetMemblockSize(getData.mb) < (getData.offset + 4)) //Memblock is smaller than current position plus size of data
readmb_size_errtxt as string = ""
readmb_size_errtxt = "Error: Memblock (" + str(getData.mb) + ") to small" + chr(10)
readmb_size_errtxt = readmb_size_errtxt + "MB size = " + str(GetMemblockSize(getData.mb)) + chr(10)
readmb_size_errtxt = readmb_size_errtxt + "Current Offset = " + str(getData.offset) + chr(10)
readmb_size_errtxt = readmb_size_errtxt + "Requested bytes = " + str(getSize)
Message(readmb_size_errtxt)
endif
endfunction
//Keep all data indexs as multiples of 4
function checkDataMod4(getData as filemb_dataValueType)
//Fix offset
if (Mod(getData.offset, 4) <> 0)
getData.offset = makeMod4(getData.offset)
endif
//Mix memory block size
if (Mod(GetMemblockSize(getData.mb), 4) <> 0)
getData.mb = makeMod4MB(getData.mb)
endif
endfunction getData
//Fixes offset so multiple of 4
function makeMod4(getNumber as integer)
if (Mod(getNumber, 4) <> 0)
while (Mod(getNumber, 4) <> 0)
getNumber = getNumber + 1
endwhile
endif
endfunction getNumber
//Fixes memblock size so multiple of 4
function makeMod4MB(getMB as integer)
mm4mb_tempSize as integer = 0
mm4mb_tempSize = GetMemblockSize(getMB)
if (Mod(mm4mb_tempSize, 4) <> 0)
while (Mod(mm4mb_tempSize, 4) <> 0)
mm4mb_tempSize = mm4mb_tempSize + 1
endwhile
endif
mm4mb_tempMB as integer = 0
mm4mb_tempMB = CreateMemblock(mm4mb_tempSize)
CopyMemblock(getMB, mm4mb_tempMB, 0, 0, GetMemblockSize(getMB))
DeleteMemblock(getMB)
endfunction mm4mb_tempMB
Check out Automation on Steam now
https://store.steampowered.com/app/1698690/Automation/