Technology Demo

File System Demo

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Spot for images

Intro

This tutorial will show you how to use Luster's file system to select a file from your computer or to write a file. For our example, we'll read in an XML file that will determine some cubes, and then save a file that determines other cubes.

Setting Up

To set up for this demo, we need to set up the camera's z distance, create an array to hold the blocks, and then set up the event listeners.

self.camera.z = 10
-- Store placed blocks
self.blocks = {}
self:addEventListener(luster.display.DisplayObject.KEY_DOWN, Function(self, self.onKeyDown))
self:addEventListener(luster.display.DisplayObject.MOUSE_DOWN, Function(self,self.onMouseDown))

Next, we need to set up the onMouseDown and onKeyDown methods so that we can move into loading and saving.

function Root:onKeyDown(key)
   if key == luster.KeyCodes.O then 
      self:onOpen()
   elseif key == luster.KeyCodes.S then
      self:onSave()
   end
end

This simply says that if the user presses "O", then the open method will be called, and if the "S" key is pressed, save will be called.

function Root:onMouseDown(button)
   if button == 0 then
      local block = luster.display.Entity(luster.resources.Mesh.CUBE)
      self:addChild(block)
      block.scale = 0.01
      local testX = math.random(-3,3)
      local testY = math.random(-3,3)
      local testZ = math.random(-3,0)
      local pos = luster.Vector3(testX, testY, testZ)
      block.position = pos
      table.insert(self.blocks, block)
   end
end

This is just a placeholder method that puts blocks in random places when the mouse is clicked. The 'if button == 0' line refers to the left mouse button. The right mouse button would equal 1. It creates a block, adds it to the stage and sets the scale, then sets the x, y, and z to equal a random number between -3 and 3 or -3 and 0. It then adds the block to the blocks array.

Selecting & Reading a File

Two methods will be needed to make things more organized. The first method, onOpen, will be called when the user presses "O" on their keyboard - this can just as easily be tied to a flash interface or anything else. For this tutorial, we'll just leave it as "O".

function Root:onOpen()
   local files = luster.filesystem.showSelectFileDialog(true, false)
   if files and #files > 0 then
      self:load(files[1])
   end
end

Here, a local variable called "files" is created and given the result of the file selection. The 'luster.filesystem.showSelectFileDialog' takes two parameters - the first true states that we are opening a file, and the second is the boolean that determines if multiple files can be opened.

The next line states that if files isn't null (in other words, if the user selected a file in the dialog), and the number of files is greater than 0, then the file path becomes the first file in the list of files. Lua's indices start at 1, instead of 0 like some other languages. Then, we call the load method with the file as the parameter.

Next, we'll move on the the load method, which we'll break up into smaller pieces.

function Root:load(path)
   local file = luster.filesystem.File(path)
   local stream = luster.filesystem.FileStream()

end

Here, we create a new variable called file, which takes the path that the onOpen method passed in. Next, we create a new stream which will read in all the information stored in a file.

if stream:open(file, "r") then
   local numBlocks = stream:readUnsignedInt()
   for i=1, numBlocks do
      local pos = luster.Vector3(stream:readFloat(), stream:readFloat(), stream:readFloat())
      local block = luster.display.Entity(luster.resources.Mesh.CUBE)
      self:addChild(block)
      block.scale = 0.1
      block.position = pos
      table.insert(self.blocks, block)
   end
   stream:close()
end

This if statement goes below the stream creation. Here, the stream calls the open method on the file, and if the file is of the right type, then the numBlocks variable will be set and we begin reading the blocks in. You can have your files store different kinds of information, but for this demo it will only read and write blocks and their positions.

In the for statements, we start at one tell it to go through all of the blocks - or 'numBlocks'. For each one, it creates a pos variable to store the position of the block. It reads in three floats representing the x,y, and z positions. It then creates a block with the generic cube mesh, adds it to the stage, and sets it scale and position. Finally, it puts the new block into the blocks array and closes the stream once all of the blocks have been read in.

Saving a File

To save a file requires similar methods, although instead of reading in files, you will be writing out to them.

The onSave method has almost exactly the same code as onOpen, except for a few differences. The showSelectFileDialog method takes the parameters "false" and "false", meaning that the dialog will not be opening any files and there cannot be multiple files. If there's a file name entered, then it calls the save method with the file as the parameter.

There is no default file type for saving, so when you name your file, be sure to include the extension (.xml) in the name. If you forget to add the extension, you can open up the file location and rename the file to include the extension.

function Root:onSave()
   local files = luster.filesystem.showSelectFileDialog(false, false)
   if files and #files > 0 then
      self:save(files[1])
   end
end

The save method writes out information to a file. Here, the beginning looks very similar to the load, except for the open method having the parameters file and "w" for write. In the if statement, we write the number of blocks as an UnsignedInt and then have a for statement that goes through all of the blocks and records writes their positions out as floats. When all of the blocks have been accounted for, the stream closes.

function Root:save(path)
   local file = luster.filesystem.File(path)
   local stream = luster.filesystem.FileStream()
   if stream:open(file, "w") then
      -- Write number of blocks
      stream:writeUnsignedInt(#self.blocks)
      -- Write out each block position
      for _,v in ipairs(self.blocks) do
         local pos = v.position
         stream:writeFloat(pos.x)
         stream:writeFloat(pos.y)
         stream:writeFloat(pos.z)
      end
   stream:close()
   end
end