Cross Platform File IO
Reading and writing files is an essential part of any application – and games are no exception. So File IO should definitely be a part our ideal game engine, meaning the engine needs a good cross platform way of reading and writing files. Should be simple right? We can just use the C# namespace System.IO right? Unfortunately, no.
What’s wrong with System.IO?
System.IO is great but it has a few problems that prevents it from being the perfect solution for a cross platform application:
- Windows 8 Store Apps don’t use System.IO
- Applications, including games, should only have access to a limited number of directories
This is becoming increasingly important as operating systems crack down on incorrectly written code that may endanger the security of the system. For example the iOS development guide has the following quote:
“For security purposes, an iOS app has limited a number of places where it can write its data.”
The list and location of the directories applications can access varies by platform so it can quickly become difficult to write good cross platform File IO code using System.IO.
- Treating paths as strings is dangerous
As this blog post from Twisted Oak Studios explains – treating paths as strings is dangerous. Since System.IO treats paths as strings it forces end users to write dangerous code.
Due to these problems the engine will provide a cross platform File IO solution. This solution will have asynchronous file IO and cross platform paths – both features are expanded on below.
Asynchronous File IO
The first requirement of the engine’s File IO solution is to have asynchronous File IO. Asynchronous File IO is very important because File IO is a relatively slow task.
“Asynchronous operations enable you to perform resource-intensive I/O operations without blocking the main thread. This performance consideration is particularly important in a Windows Store app or desktop app where a time-consuming stream operation can block the UI thread and make your app appear as if it is not working.”
The details of implementing Asynchronous File IO can vary from platform to platform, because not all platforms support the same version of the C# framework.
Cross Platform Paths
The second requirement of the engine’s File IO solution is to have good cross platform paths. This will be accomplished with the engine’s Path object.
As seen above the Path uses a Location enumeration to determine what the root of the path is. This automatically and easily restricts the directories that File IO can occur in without worrying about the specifics of each platform.
Also, the Path object validates each piece of the path that is sent to it – checking to make the user isn’t supplying invalid strings. In addition, it makes sure the user isn’t trying to send the Path object a path as string. For instance, a user might try to use:
new Path(Location.Local, “Path/Subdirectory/Filename”);
Instead of:
new Path(Location.Local, “Path”, “Subdirectory”, “Filename”);
These lines of code look almost identical but only the second line of code is guaranteed to work on every platform. So, the Path object makes sure to just ignore invalid paths – hopefully causing the program to crash informing the programmer of their mistake. Later, we will have a debug system in place that will give us a better solution - but until then this will have to do.
Additionally, the Path object stores the filename separately from the directories – this makes it significantly easily to figure out what part of the path is supposed to represent the filename or if there is a filename at all.
Lastly, the Path object stores directories as a list – this makes adding another directory as simple as adding another string to the list. It also hugely simplifies comparing, combining, and modifying paths.
Side Note: See Does a List guarantee that items will be returned in the order they were added? for discussion on whether List can safely be used when order is important.
Also, note that the Path object can turn the object back into a raw string path – but this method is intended for use inside the engine only. That is what the internal keyword does - it prevents the user created code, which will be in a different assembly than the engine code, from being able to use the GetPlatformPath() method in the Path object.
The Solution
By putting together our asynchronous methods and by using the new engine Path object it is easy to make a new cross platform File IO solution that currently looks as follows:
The conflict options enumeration – is a simple way of informing the File IO solution what to do if a naming conflict was found. For instance, imagine that you wanted to write a file named ‘Test.txt’ but there already was a file named ‘Test.txt’ there. What should the engine do? Should the engine – replace the file or fail? There could be even more possibilities such as renaming the old file, or generating a new file name for the new file. The conflict options enumeration provides the user that choice.
As seen above the File and Directory static classes look somewhat like the System.IO.File and System.IO.Directory static classes but with the engine’s Path object instead of strings.
One of the first things to note, is the weird structure of the public static calls, this is because partial methods in C# cannot have a return value so the code must pass the value it wants back as a reference value and then return that value. This is a minor inconvenience that can be hidden from the end users, while still allowing the platform specific code to be separated into platform specific files.
Another thing to note is that the File static class has no exists method – this is intentional. As pointed out in this StackExchange post – there is no good way of implementing a File.Exists method asynchronously.
Conclusion
Please note that the code in this blog is not complete and is missing some of the methods a complete File IO solution would need – such as getting the file names in a directory, or reading from a file in discrete bursts instead of all in one go, or even adding a new Conflict Option that generates a new name for the file when a conflict is detected. This is due to current time restrains – but expanding the solution to handle those situations is a fairly simple matter.
That said, the File IO solution should be an excellent framework for any File IO the engine needs to do. Which leads us into the topic for next week’s blog: Cross Platform Game Engine Series – The Debug System.
References
- http://msdn.microsoft.com/en-us/library/System.IO(v=vs.110).aspx
- http://msdn.microsoft.com/en-us/library/windows/apps/windows.storage.aspx
- https://developer.apple.com/library/mac/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
- http://twistedoakstudios.com/blog/Post4872_dont-treat-paths-like-strings
- http://msdn.microsoft.com/en-us/library/kztecsys(v=vs.110).aspx
- http://stackoverflow.com/questions/453006/does-a-listt-guarantee-that-items-will-be-returned-in-the-order-they-were-adde
- http://msdn.microsoft.com/en-us/library/7c5ka91b.aspx
- http://msdn.microsoft.com/en-us/library/system.io.file(v=vs.110).aspx
- http://msdn.microsoft.com/en-us/library/system.io.directory(v=vs.110).aspx
- http://stackoverflow.com/questions/19076652/check-if-a-file-exists-async/19077180#19077180
No comments:
Post a Comment