Showing posts with label saveGlobalData. Show all posts
Showing posts with label saveGlobalData. Show all posts

Thursday, September 20, 2012

Tutorial 17 - A Simple File Manager for Codea



17.1 The Main Class

         
Tutorial 16 explained the various ways that data can be stored using Codea. In order to facilitate the loading and storing of data for our level generator we created a simple file management class.
    
This version handles Local Data, Project Data, Project Information and Global Data. Remember that all of this data is stored in string format using a pList which is accessed by a unique string called the key.
    
The File Manager is constructed using two ListScroll objects (a class written by sanit over on the Codea Forum), a text box showing the value of the selected key and a MenuBar object (a class written by dave1707 also from the Codea Forum).
   
The Main class is used to load some test data into the four data stores and then instantiate the FileManager class. We have provided stubs for the functions called when the MenuBar items are tapped. The only menu item which has been implemented is delete file. WARNING - file deletion can NOT be undone and there is no confirmation required to delete a file so back up your data and use this class with care (or delete this functionality). Feel free to delete the test data which is loaded in setup(), this will be recreated every time you run the App.
  
The data you need to take particular care with is Global Data as this may have been stored by another Codea App. For example, if you have used Spritely (the sprite creation example program which comes with Codea) then any sprites that you have created will be stored in global data.

The delete function has not been implemented for Project Info because there is no way to determine what keys are in there (i.e. there is no listProjectInfo function).

To delete a file we just assign the value of the selected key to nil. We will provide examples of the load and save function when we incorporate FileManager into our Tower Defence Level Generator (dGenerator).
   
The line FileManager:init() will setup your FileManager class and prefetch the keys for each persistent data store.
       
The class handles both portrait and landscape orientation. The orientationChanged() function adjusts the FileManager position when the iPad is rotated. It works but not very well as ListScroll wasn't designed to handle this. You may have to drag the ListScroll box to show the contents. We haven't had a chance to modify this.
      

17.2 The FileManager Class

             
Most of the hard work is done in this class. We start off by defining variables to represent the four supported Directory Types to make the code easier to read. The init() function creates the two ListScrolls (one for the list of Directories called directoryList and one for the keys it contains called dataKeyList).
     
We then pre-load the keys for Project Data, Local Data and Global Data (we already know the Project Info keys since we loaded them). We set the current directory to Project Info and set the current key to the first one. There is a variable called valueString which we use to contain the value of the selected string. Only the first 150 characters of the saved value are displayed. The last thing we do in init() is setup the MenuBar.
     
One of the modifications we had to make to the MenuBar class was to prevent touches on the ListScroll when a sub menu is displayed (otherwise you could accidentally delete the wrong file).
       

17.3 Download the Code

          
You can download all of the code for this program using the following links:
  1. FileManager Complete Code v1.1 - All of the code in one file.
  2. Main.lua - The Main tab class only.
  3. FileManager.lua - The FileManager v1.1 class only.
  4. ListScroll.lua - The ListScroll class from sanit.
  5. TextItem.lua - The TextItem class used in ListScroll from sanit.
  6. MenuBar.lua - The MenuBar class v1.1 from dave1707 (modified) .
Next up we will incorporate FileManager into our Tower Defence Level Generator.

Saturday, September 8, 2012

Tutorial 16 - Convert String to Table and Table to String

16.1 Why do we need this?

  
In our last tutorial we developed a simple level editor. This is not much use unless you can save and then load the level data produced. Codea provides a function to saveProjectData and one to saveGlobalData(). Since we would like to save our level data from dGenerator and then read it into our game program, saveGlobalData() is the function we need, as saveProjectData() saves data which is only accessible by the program which saved it.
  
Unfortunately it is not as simple as saying saveGlobalData("Level1", ourTableData) as you can only use this function to save a number, string or nil. Consequently, we need to convert our table to a string in order to save it. To load the data we need to reverse this process and reconstitute our table from the saved string.
  
We used a similar technique in MineSweeper to save and load high scores, albeit we were only concatenating three strings rather than converting a table, but the theory is the same.
     

16.2 Codea Data Persistence Under the Hood

      
There are four primary ways to save and load data in Codea (excluding image data, which we wont discuss in this tutorial). Namely:
  1. clearLocalData(), saveLocalData() and readLocalData() - used for storing information which is unique to the program and the device. Other programs and devices running a copy of the same program don't have access to this data. It is useful for things like high scores and perhaps user preferences. For each project, a key of the form Project Name_DATA is used to store local data in the com.twolivesleft.Codify.plist which may be found in the Library -> Preferences sub folder (i.e. the same location as global data).
  2. clearProjectData(), saveProjectData and readProjectData() - gets bundled with your program and is not device specific. It can be used for level data, maps, and other program specific data which doesn't change with different users. Project Data is stored in the Data pList in the Documents directory of your App.
  3. saveProjectInfo() and readProjectInfo - has similar access to Project Data (bundled with program and is not device specific) but is used for saving metadata about your App. There are two data keys which are natively supported by Codea, "Description" and "Author". The data associated with the "Description" key will show in the Codea Project Browser when this App is selected. If you are planning on using the Codea runtime, be aware that this uses the "Version" key and expects a string. If you use a number like we do then you will get an error and the runtime wont compile. The fix for this is described in our earlier tutorial on submitting an application to App store. If you whip out iExplorer you will see that all of the project information is stored in Info.plist which is part of your application bundle. This pList also includes a key called "Buffer Order" which tells the runtime which order your tabs should be loaded in.
  4. saveGlobalData() and readGlobalData() - is data available between all projects on a device. The subfolder Preferences of folder Library contains files including com.twolivesleft.Codify.plist which contains any global data that you have saved (under the key "CODEA_GLOBAL_DATA_STORE"). If you have used the sprite generator Spritely which comes with Codea, you will see that it stores some data in the global data store.
You can read more about what is happening under the hood on the Codea Wiki. Since version 1.4.3 of Codea three additional functions have been provided: listLocalData(), listProjectData() and listGlobalData(). These return the keys already saved in these data stores.
    

16.3 A Solution for Single Dimension Arrays & Tables

    
The simplest version of a Lua table is an array so lets work out a solution for that first. It is an easy matter to concatenate the elements of a table together to form a string, as long as the elements of that table are something that can be converted to a string (i.e. this wont work on a table of tables). It is easy because Lua provides a function to do this very thing. Even better you can specify a delimiter to be placed between each element in the resulting string.
  
The function looks like table.concat(yourArray, "delimiter") so:
  
table.concat({ "one", "two", "three", "four", "five" }, ",") will produce a string "one,two,three,four,five"
   
Assuming we pass in the array to our saveData() function, it will look like:
  
function saveData(mArray)
   
    local saveString = table.concat(mArray, ",")
    saveGlobalData("dataKey", saveString)
     
end
   
Obviously you could just as easily use a global array, in which case you wont need to pass the array to the function. Loading the data is a bit more work, but as with a lot of common problems, it is one which has already been solved (credit to http://richard.warburton.it for writing the explode function which does all the heavy lifting).
      
function explode(div,str) 
    
    if (div=='') then return false end
    local pos,arr = 0,{}
    -- for each divider found
    for st,sp in function() return string.find(str,div,pos,true) end do
        table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
        pos = sp + 1 -- Jump past current divider
    end
    table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
    return arr
   
end
    
Using the above function the loadData() function is trivial, and will return your original array:
   
function loadData()
     
    local stringArray = readGlobalData("dataKey")
    return explode(",", stringArray)
     
end
     

16.4 A Solution for Multidimension Arrays & Tables

   
Once you get past the simple array or non-nested table things get complicated quickly. As you can stick just about anything in a table (e.g. another table, closures / functions, Userdata and Metatables) a comprehensive conversion function would need to take all of the possible cases into account. These table serialisation libraries exist but they are overkill for our purposes.
  
Instead we will use the charmingly named Pickle and UnPickle functions written by Steve Dekorte.
    
To be continued...