## I/O bottleneck For example, the Red Alert 3 team at Electronic Arts observed that writing data into log files was causing significant performance degradation. They changed the logging system so that it accumulated its output into a memory buffer, writing the buffer out to disk only when it was filled. Then they moved the buffer dump routine out into a separate thread to avoid stalling the main game loop. ## Asynchronous File I/O Streaming refers to the act of loading data in the background while the main program continues to run. Many games provide the player with a seam- less, load-screen-free playing experience by streaming data for upcoming lev- els from the DVD-ROM, Blu-ray disk or hard drive while the game is being played. Audio and texture data are probably the most commonly streamed types of data, but any type of data can be streamed, including geometry, level layouts and animation clips. * Can write yourself by calling the blocking I/O on a separate thread then signaling/ semaphoring when its complete ## Resource Management (external to the engine)/Revision Control * For games with alot of and large assets, revision control is important for managing different versions of data; however this can create huge space requirements and be slow/inefficient * Naughty dog uses a custom tool that uses UNIX symbolic links w/ version control, so the user doesnt have to have the entire set of assets on disk - just the ones they need to work on, then the symbolic links are updated afterwords * Resource Database: contains metadata and list for all assets (e.g. how its interpreted or converted by the engine) * Should have ability to add and delete resources, change resource location, revision history, etc. >It should be pretty obvious from looking at the above list that creating a reliable and robust resource database is no small task. When designed well and implemented properly, the resource database can quite literally make the difference between a team that ships a hit game and a team that spins its wheels for 18 months before being forced by management to abandon the project (or worse). I know this to be true, because I’ve personally experienced both. ## Asset conditioning pipeline * Converts assets to the appropriate format for the engine * Resource Linking - combining multiple assets into a single file/package ## Resource Manager (internal engine code) When loading data from files, the three biggest costs are seek times (i.e., moving the read head to the correct place on the physical media), the time required to open each individual file, and the time to read the data from the file into memory When a single large file is used, all of these costs are minimized. A single file can be organized sequentially on the disk, reducing seek times to a minimum. And with only one file to open, the cost of opening individual resource files is eliminated Solid-state drives (SSD) do not suffer from the seek time problems that plague spinning media like DVDs, Blu-ray discs and hard disc drives (HDD). However, no game console to date includes a solid-state drive as the primary fixed storage device (not even the PS4 and Xbox One) Storing resources as a ZIP file is advantageous: * Open format (free ZIP library) * Virtual files within zip archive "remember" their relative paths * Can be compressed to save space and makes disk reading faster (since less to read) - cost of decompression can outweigh read time, e.g. of CD/DVD * ZIP files are modular - easy to replace different versions Every resource in a game must have some kind of globally unique identifier (GUID). The most common choice of GUID is the resource’s file system path (stored either as a string or a 32-bit hash) ## Resource Manager: memory management * Common to use the memory allocators described prev chapter (stack allocator, pool allocator) * In the case of stack allocators, levels can be loaded into the stack then freed once the level is over * For pool allocators (commonly used for asset streaming (async loading)), store assets in equal size chunks. Files can be loaded as non-contigous chunks by using a linked list: each chunk points to the next chunk * ![Resource Chunk Diagram](chunk_diagram.png) * Note this requires "chunkiness" in mind when creating the resources * Naughty Dog used 512KiB chunks on PS3 and 1 MiB on PS4 ## Post-load processing/initialization * Common for resources to need additional work/initialization done after they have been loaded from disk/memory * C++ Object can be stored/loaded to and from a file then reconstructed * Uses "placement new" syntax void *pObject = ConvertOffsetToPointer(objectOffset, pAddressOfFileImage); ::new(pObject) ClassName; // placement new syntax * Calls the constructor on a preallocated block of memory * Example Code: // TestClass.h \#ifndef TEST_CLASS_H_INCLUDED \#define TEST_CLASS_H_INCLUDED \#include \ class TestClass { public: TestClass() { std::cout \<\< "Initial A,B: " \<\< a \<\< ", " \<\< b \<\< std::endl; a = 5; b = 4; std::cout \<\< "Initted A,B: " \<\< a \<\< ", " \<\< b \<\< std::endl; } void myfunc2() { a -= 7; b += a; std::cout \<\< "a,b after myfunc2: " \<\< a \<\< ", " \<\< b \<\< std::endl; } private: int a; int b; }; \#endif // end TEST_CLASS_H_INCLUDED // create.cpp \#include \ \#include \ \#include "TestClass.h" int main(void) { TestClass testClass; testClass.myfunc2(); std::ofstream outfile("TestClassBinary.bin"); if (!outfile.is_open()) { std::cerr \<\< "Error opening file" \<\< std::endl; return -1; } outfile.write((const char*)&testClass, sizeof(TestClass)); outfile.close(); return 0; } // read.cpp \#include \ \#include \ \#include "TestClass.h" int main(void) { TestClass testClass; std::ifstream infile("TestClassBinary.bin"); if (!infile.is_open()) { std::cerr \<\< "Error opening file" \<\< std::endl; return -1; } infile.read((char*)&testClass, sizeof(TestClass)); infile.close(); // call the constructor on our read in object std::cout \<\< "Calling constructor on object" \<\< std::endl; ::new((void*)&testClass) TestClass; return 0; } * Another important note is that if you have internal class pointers, those will be invalidated after read/write to/from disk, and instead need to be converted to "File Offsets" assuming the pointer data is written to disk on the same file