-----------------------------
MANA PACKAGE SYSTEM
-----------------------------
1. INTRODUCTION
2. LOCATION OF DATA
3. CONTENTS OF DATA PACKAGE
4. TYPES OF DATA
5. INITIALIZING PACKAGE MANAGEMENT
6. LOADING A REQUESTED RESOURCE
7. RESOURCE MANAGEMENT DETAILS
1. INTRODUCTION
Mana is expected to grow continuously with updates to the game world
occurring relatively frequently. More often so than for example new releases
of the game client. To make sure players don't have to update their data
manually all the time, by for example downloading the latest from the website,
the Mana client should be able to automatically obtain new data packages from
the server.
Note: To reduce the load on the server (which isn't expected to have huge
free uploading resources), the idea is that the server will only send a
torrent file to the client and that the file is subsequently downloaded from
several locations that have volunteered to spread Mana data files. Ultimately
a simple option on the client will even allow players to contribute their
excess bandwidth to help other players get the updates faster.
2. LOCATION OF DATA
There are two locations where Mana can look for game data. The install data
directory and the data directory in the user's home directory. The latter one
doesn't have to be used for Windows users, but is required for dynamic updates
for UNIX users, who generally won't have write permissions to the install
data directory. So for UNIX the two locations are:
/usr/local/share/manaworld/data/*
~/.manaworld/data/*
While for Windows all the data will be located at:
C:\Program Files\Mana\data\*
In the UNIX case it doesn't matter in which order the data directories are
examined.
3. CONTENTS OF DATA PACKAGE
The contents of the data packages are strictly categorized and all packages
share a single root, similar to the paths on a UNIX system. The name of the
package is irrelevant. An example of the contents is given by:
/graphics/sprites/forest/pinetree.png
/graphics/sprites/furniture/bed.png
/graphics/tiles/dark_forest.png
/graphics/tiles/city.png
/music/eagles_are_watching.xm
/music/silent_rose.xm
/sound/battle/sword1.ogg
/sound/battle/sword2.ogg
/maps/deep_desert.tmx
/maps/desert_town.tmx
/tilesets/dark_forest.tsx
/tilesets/city.tsx
/scripts/Portal.rb
/scripts/PawnShop.rb
/scripts/Fountain.rb
4. TYPES OF DATA
png - The preferred format for images
xm - The preferred format for music (or other kinds of module formats)
ogg - The preferred format for sound effects
tmx - The map format (to be implemented)
tsx - The tile set format (to be implemented)
rb - A Ruby script file (application to be discussed)
5. INITIALIZING PACKAGE MANAGEMENT
When Mana starts it will scan its data directories for both packages (archives)
and directories. When a directory is found with the same name as a package, the
directory is the preferred location to load data from as it is assumed to be
more up to date.
Each package will have an ID and a file listing associated with it. Having made
a list of all packages they are processed in the order of their IDs. A mapping
is made from file to package, as follows:
/music/eagles_are_watching.xm -> /usr/local/share/manaworld/data/musicpack
/music/silent_rose.xm -> /usr/local/share/manaworld/data/musicpack
/sound/battle/sword1.ogg -> ~/.manaworld/data/patch1
/sound/battle/sword2.ogg -> ~/.manaworld/data/patch1
...
Because the packages are loaded in the order of their IDs, it is made sure that
each file will always point to the package in which is was last updated. The
package IDs make sure that there is an absolute ordering of the packages.
To allow the client to get rid of old packages, a package can declare an
arbitrary amount of packages with a lower ID than itself as obsolete. These
packages will then be ignored by the client, and optionally they can be
automatically deleted.
6. LOADING A REQUESTED RESOURCE
When the game starts and during the game, resources will continuously be asked
for. A resource manager will take care that each resource is only loaded once.
It also makes sure that the resources are loaded from the right package using
the constructed mapping.
As noted above, the resource manager makes sure directories are preferred
to package files when resources are loaded. The presence of directories is
only expected in the case of developers that will relatively frequently update
the data while working on the next package to be released.
7. RESOURCE MANAGEMENT DETAILS
The resource management technique is critical to the overall success of the
package management system as a whole. Resources are loaded at runtime as they
are needed, and unloaded as they become unused. In order to ensure the
autonomous functioning of this process reference counting is the agreed upon
technique for managing loaded resources in Mana.
For those unfamiliar with the practice of reference counting, it involves
every resource object having a variable containing the number of references to
the object. When a reference is added the function addRef() is called and when
it is removed the function release() is called. When the reference count
reaches zero the object will automatically delete itself, thus handling the
cleanup of resources.
Reference counting will form the core of the resource management system. Each
resource object will have the functionality of a reference counted object. The
resource manager will hold ResourceEntry objects. The resource entry object
contains a pointer to the resource as well as the location of the path of the
file the resource was loaded from. This would look something like:
/**
* A generic reference counted resource object.
*/
class Resource {
public:
/**
* Loads the resource from the specified path.
* @param filePath The path to the file to be loaded.
* @return true
if loaded false
otherwise.
*/
virtual bool Load(std::string filePath) = 0;
...
/**
* Increments the reference counted of this object.
*/
void addRef() { ++referenceCount; }
/**
* Decrements the reference count and deletes the object
* if no references are left.
* @return true
if the object was deleted
* false
otherwise.
*/
void release() {
--referenceCount;
if (!referenceCount)
{
delete this;
return true;
}
return false;
}
private:
unsigned int referenceCount;
};
...
/**
* A resource entry descriptor.
*/
struct ResourceEntry {
Resource* resource;
std::string filePath;
};
...
The resource manager would then hold a mapping containing the resource entry as
well as the string defining its resource identification path. The resource
manager would thus look something like this:
/**
* A class for loading and managing resources.
*/
class ResourceManager {
public:
...
private:
std::map resources;
};
...
This will allow the game to load resources with little awareness of the actual
path from which they were loaded. The resource manager will also act as a
resource object factory. A factory object is an object that creates an
instance of an object derived from a common base class. In this case it will
create Resource objects. This would make the ResourceManager object look like
this:
/**
* A class for loading and managing resources.
*/
class ResourceManager {
public:
enum E_RESOURCE_TYPE
{
MAP,
MUSIC,
IMAGE,
SCRIPT,
TILESET,
SOUND_EFFECT
};
/**
* Creates a resource and adds it to the resource map.
* The idPath is converted into the appropriate path
* for the current operating system and the resource
* is loaded.
* @param type The type of resource to load.
* @param idPath The resource identifier path.
* @return A valid resource or NULL
if
* the resource could not be loaded.
*/
Resource* Create(const E_RESOURCE_TYPE& type,
std::string idPath);
...
private:
std::map resources;
};
...
Loading a resource would then look something like:
Image* img = (Image*) ResourceManager.Create(ResourceManager::IMAGE,
"/graphics/tiles/dark_forest.png");