How will resources work in detail?

What do you want to see in Armagetron soon? Any new feature ideas? Let's ponder these ground breaking ideas...
User avatar
wrtlprnft
Reverse Outside Corner Grinder
Posts: 1679
Joined: Wed Jan 04, 2006 4:42 am
Location: 0x08048000
Contact:

How will resources work in detail?

Post by wrtlprnft »

First of all, what I think the outcome of the resource thread was:

Resources consist of the xml file describing them and optional binary files in a structure like this:

Code: Select all

author/resource_name-version.aatype.xml 
author/resource_name-version.aatype/somebinaryfile.png
The resource system takes care of downloading and caching the files and providing a user interface to select them.

So, how exactly will that work?
Here's my idea: The main resource file should have a section that helps the system figure out which files to download, like this:

Code: Select all

<Resource [attributes]>
 <Files>
  <File type="texture" src="image.png" id="rim_wall" />
  <File type="sound" src="music.ogg" id="crashsound" />
 <Files>
 <!-- contents of the file -->
</Resource>
So, once that file gets loaded the resource manager will load, open and read those two files, store them in an appropiate data structure and offer a std::map<tString, datatype> so the code that's actually using the resource can access the files it needs by the id attribute of the <Files> section.

So, how should those storage classes be organized? Most of them can't sit at the src/tools level, but tResourceManager sits there... So either we move the whole thing out of tools into tron which is probably not such a good idea either since things like textures get used in many areas of the code, or we find a solution that lets the resource file classes "register" themselves for a type.
My solution is to have a generic (dummy) tResourceFile class at tools level that would get inherited later. Such an inherited^^ class would then register itself in a std::map<tString, tResourceFile *(tString const &)> that would store a static function within that class returning a new instance with a specific filename (yes, I like std::maps). Then the tResourceManager class would parse the type attribute of each file and call the appropiate object containing it. That way the actual file classes can be spread out everywhere in the sources (so the texture and model would go into render, and the sound files into engine).

A comment on converting old- style movie- and soundpacks to resources: We could just provide a template XML file that would reference the textures/models/sounds under the standard file names, so all a moviepack author has to do to update his pack is to edit that file, put in a custom author and moviepack name and commit. We could even provide some online tool for that.

I hope I made some sense. Any comments/suggestions or am I totally on the wrong track?
There's no place like ::1
User avatar
Lucifer
Project Developer
Posts: 8640
Joined: Sun Aug 15, 2004 3:32 pm
Location: Republic of Texas
Contact:

Post by Lucifer »

Here's what I"m thinking. I'll try not to make this post into a book, if y'all like it I can put it on the wiki (where I will make it a book).

Ok, resources need their own directory. Right now, each directory under src represents a layer and they all stack on top of each other. I'd like to move it to where each represents a component, and they may be stacked, maybe not. Anyway, the resource system doesn't belong in tools, it needs access to stuff tools don't have access to.

The resource system is our own internal platform-independent way to read files from disk, and it is significantly augmented with our own method of organizing the files we want to read. So it should have access to SDL and os-specific file reading/writing.

We have tResourceManager. In my view, it handles retrieving files from the cache/downloading, and will handle timing out cached resources and deleting them. It transparently handles bundled resources, installed resources, and cached resources, so none of the other components need care about it.

To help tResourceManager do it's work, we have tXmlResource. Currently there's nothing in tXmlResource, it's a placeholder. What it *should* contain, however, is the ability to read tags that are standard across all resources, such as the parent <Resource> tag. It should also understand tags that reference external files and, imo, provide methods for opening filetypes that are common amongst many components. This includes textures and models, as far as I'm concerned. So the code path to loading files into memory passes through tXmlResource. For an initial implementation, we can just put the actual code into tXmlResource, later we can work out a fancy way to register mimetype handlers to allow components to hook in their own special formats.

tResourceManager, then, will scan all resource directories creating its own internal list of tXmlResources, and it will use that list to do its work. Same for installing, when tResourceManager is called upon to install a resource, it will be given the current location of the resource, load it into a tXmlResource, and then do what it needs to do to install it.

Components will create their own classes as needed that inherit tXmlResource. Then they'll register that class with tResourceManager. When it's time for a component to request a resource, it will ask tResourceManager for an instance of that resource and tResourceManager will provide it, either by creating a new one, or by dynamic_casting the one from its list, or whatever else we dream up.

So the rest of the game, then, will only access resources through tResourceManager. No component will create new instances of a resource, all will always get points from tResourceManager. tResourceManager will always retain ownership of the resource instances, so components will not delete them, ever.

I think that's it. I've some ideas for how the interface will look from tXmlResource, and the interface for tResourceManager is pretty self-explanatory, it'll probably write itself.

The important aspects, in my mind, are these:

tXmlResource provides a generic way to handle all resources using standard tags that exist in all xml files. It also provides a base class for components to use to implement their own special resources.

An internal registry will exist eventually that components will use to tell the resource system about their own special resources, so that when they query tResourceManager for their own special resources, tResourceManager can provide.

These two aspects combine to make it easy to add new resources, both in new code and potentially in scripting. All that's needed is to make your subclass of tXmlResource and register it with tResourceManager. We also get comprehensive resource management within tResourceManager via tXmlResource, and this is true for even new resource types, and we get this without sacrificing extensibility.

Potential downsides: Since components will be able to provide their own file loaders (in the long run, mind you), then loading files will tend to be scattered. Also, it may not be possible to enforce an internal format for files (stuff stored in memory) that can be used by other components that use similar types of data. Still, a pattern will emerge that will make it easy to find all such mechanisms, and through good coding tactics we can certainly keep ourselves organized enough so that while the file loaders are spread out, they're easy to identify and straightforward to maintain. There may also be a performance cost, but I strongly suspect that at this level performance isn't a serious issue anyway. Considering what we get in return, I think it's better to pay the performance price anyway.
Image

Be the devil's own, Lucifer's my name.
- Iron Maiden
Luke-Jr
Dr Z Level
Posts: 2246
Joined: Sun Mar 20, 2005 4:03 pm
Location: IM: [email protected]

Re: How will resources work in detail?

Post by Luke-Jr »

wrtlprnft wrote:First of all, what I think the outcome of the resource thread was:
I don't remember having an actual outcome, but this works well to an extent.
wrtlprnft wrote:Here's my idea: The main resource file should have a section that helps the system figure out which files to download, like this:
Textures (and sound?) are part of the appearance CSS/style applied to the map. Thus, it would in practice be more like this:

Code: Select all

Wall.rim {
    texture: data(image.png);
}
Wall:collision {
    sound: data(music.ogg);
}
In the case of references that do belong in an XML file, the attributes 'datasrc' (embedded data), 'src' (embedded external resource) or 'href' (linked external resource) could be used.
wrtlprnft wrote:So, once that file gets loaded the resource manager will load, open and read those two files, store them in an appropiate data structure and offer a std::map<tString, datatype> so the code that's actually using the resource can access the files it needs by the id attribute of the <Files> section.
There's nothing to keep the tXmlParser from scanning the three relevant attributes and the tCSSParser from scanning the data() and resource() 'functions'.
I don't know what a std::map is, but would it support the streaming Lucifer mentioned is necessary?
wrtlprnft wrote:So, how should those storage classes be organized? Most of them can't sit at the src/tools level, but tResourceManager sits there... So either we move the whole thing out of tools into tron which is probably not such a good idea either since things like textures get used in many areas of the code, or we find a solution that lets the resource file classes "register" themselves for a type.
I like the registration concept, but I'd make it invisible by having the parent tResource class take care of it for all subclasses...
wrtlprnft wrote:My solution is to have a generic (dummy) tResourceFile class at tools level that would get inherited later. Such an inherited^^ class would then register itself in a std::map<tString, tResourceFile *(tString const &)> that would store a static function within that class returning a new instance with a specific filename (yes, I like std::maps). Then the tResourceManager class would parse the type attribute of each file and call the appropiate object containing it. That way the actual file classes can be spread out everywhere in the sources (so the texture and model would go into render, and the sound files into engine).
Sounds good, but instead of a static function, wouldn't a constructor make more sense? ;)
Lucifer wrote:Ok, resources need their own directory.
Do we need a separate directory for 'file' and 'udp' too? Generic resource code is fine where it is.
Lucifer wrote:Anyway, the resource system doesn't belong in tools, it needs access to stuff tools don't have access to.
Such as? The generic resource management stuff shouldn't need anything from any other subdirectory, or it's not as generic as it should be.
Lucifer wrote:We have tResourceManager. In my view, it handles retrieving files from the cache/downloading, and will handle timing out cached resources and deleting them. It transparently handles bundled resources, installed resources, and cached resources, so none of the other components need care about it.
It also handles loading the resources, at least to the part of getting a FILE for the rest of the code. With wrtl's ideas, it will also go a small step further and just return an object for the resource.
Lucifer wrote:To help tResourceManager do it's work, we have tXmlResource. Currently there's nothing in tXmlResource, it's a placeholder. What it *should* contain, however, is the ability to read tags that are standard across all resources, such as the parent <Resource> tag. It should also understand tags that reference external files and, imo, provide methods for opening filetypes that are common amongst many components. This includes textures and models, as far as I'm concerned. So the code path to loading files into memory passes through tXmlResource. For an initial implementation, we can just put the actual code into tXmlResource, later we can work out a fancy way to register mimetype handlers to allow components to hook in their own special formats.
Since not all resources may be XML-based, we should have one lower level of tResource, which is subclassed into tXmlResource, tCssResource, and tBinaryResource (which is never used directly). From there, tXmlResource can be subclassed into gMap and cCockpit while tCssResource can be subclassed into rAppearanceConfig, gLogicConfig, and sSoundConfig.
Lucifer wrote:Same for installing, when tResourceManager is called upon to install a resource, it will be given the current location of the resource, load it into a tXmlResource, and then do what it needs to do to install it.
Installing? As far as the game is concerned, resources are used, not installed. "Installing" (extracting or resolving dependencies of) a resource is a task for a separate application. Or at least, we shouldn't be opening a SDL game when someone double-clicks on the moviepack they downloaded...

Regarding the file format loader discussion, fTiff or such should probably be implemented as a subclass of tBinaryResource, except doing the decompression to an uncompressed format instead of a raw read. Thus, anything opening an image won't even know it's a TIFF-- it will just think it's an uncompressed image.
User avatar
Lucifer
Project Developer
Posts: 8640
Joined: Sun Aug 15, 2004 3:32 pm
Location: Republic of Texas
Contact:

Re: How will resources work in detail?

Post by Lucifer »

Luke-Jr wrote: Since not all resources may be XML-based, we should have one lower level of tResource, which is subclassed into tXmlResource, tCssResource, and tBinaryResource (which is never used directly). From there, tXmlResource can be subclassed into gMap and cCockpit while tCssResource can be subclassed into rAppearanceConfig, gLogicConfig, and sSoundConfig.
That was the rationale between the tXmlResource name, after all. :) I've no objection to adding a generic tResource in the inheritance tree as parent to tXmlResource.
Regarding the file format loader discussion, fTiff or such should probably be implemented as a subclass of tBinaryResource, except doing the decompression to an uncompressed format instead of a raw read. Thus, anything opening an image won't even know it's a TIFF-- it will just think it's an uncompressed image.
We actually continued this discussion in irc. If you've got it logged, give it a lookover, I"ll try to summarize here.

Basically, loaders would be handled as callbacks. Take a look if you haven't already at how the callbacks are handled in the cockpit, because that's what I'm thinking. The loading code would actually be in tResource (to replace tXmlResource for further discussion). When a component requests a resource, then tResource will load all the files it needs into memory (the ones that don't stream, that is). It will do it by examining all of the data it has to pick an appropriate loader. So, it knows, for example, that the given file is intended to be a "texture", and it's file extension is .png, so it searches its list for a callback that loads "textures" from .pngs.

The callback system should allow us to hook in new loaders from anywhere, but for the purposes of organization we'll try to keep all of *our* loaders in one place. During initialization, all loaders will register with the callback system either through static classes or some other mechanism. This allows components to add their own special loaders as needed, and also for scripts to add in loaders of their own, again for special formats not supported by the core.

This eliminates the need for tBinaryResource and other similar classes, and arguably eliminates the need for tXmlResource, since loading an xml file can be considered just another loader, and each component would provide its own parser for it. Not sure how I feel about that, it's a fresh idea for me, but there you go.
Image

Be the devil's own, Lucifer's my name.
- Iron Maiden
Luke-Jr
Dr Z Level
Posts: 2246
Joined: Sun Mar 20, 2005 4:03 pm
Location: IM: [email protected]

Re: How will resources work in detail?

Post by Luke-Jr »

Lucifer wrote:
Regarding the file format loader discussion, fTiff or such should probably be implemented as a subclass of tBinaryResource, except doing the decompression to an uncompressed format instead of a raw read. Thus, anything opening an image won't even know it's a TIFF-- it will just think it's an uncompressed image.
Basically, loaders would be handled as callbacks.
Callbacks are non-OO thing. I don't see how they can have any use once objects are supported. But that's just technicalities in implementation...
Lucifer wrote:The loading code would actually be in tResource (to replace tXmlResource for further discussion). When a component requests a resource, then tResource will load all the files it needs into memory (the ones that don't stream, that is). It will do it by examining all of the data it has to pick an appropriate loader. So, it knows, for example, that the given file is intended to be a "texture", and it's file extension is .png, so it searches its list for a callback that loads "textures" from .pngs.
tResource is too abstract to have any clue how to load a resource. The loading code needs to be part of the relevant subclass-- fTiff, gMap, cCockpit, etc... The origin resource used (for example, a map) would be loaded by tXmlResource+gMap. The tXmlResource code should read all the 'datasrc' attributes and request the relevant resources from the tResourceManager, which will return a new tResource object for it (in the case of a TIFF texture, a fTiff). tXmlResource should then place this object in the 'datasrc' attribute of the parsed XML before passing it to gMap for further processing. gMap will then read from the fTiff object and get uncompressed bitmap data, which should be what it is expecting.
In practice, this means that when gMap is being constructed, it will pass the actual XML parsing to tXmlParser which will encounter <AppearanceStyle src="AATeam/DefaultTextures-0.3.0.css">. Before passing 'AppearanceStyle' to gMap for processing, it will first set the 'src' attribute to tResourceManager::getResource("AATeam/DefaultTextures-0.3.0.css"). tResourceManager::getResource will obtain the resource, open it, and create and return tCssResource(filehandle).
tCssResource will, while parsing the resource, recognize 'data(static.tiff)', and replace it with tResourceManager::getResource("AATeam/DefaultTextures-0.3.0/static.tiff") internally.
When gMap encounters the AppearanceStyle, it will load its parameters onto the relevant map elements, including the fTiff reference onto the relevant wall's texture, and release/delete the tCssResource which is no longer needed, along with itself (since in the current design, it has been abstracted into other objects and is also no longer needed).
Lucifer wrote:This eliminates the need for tBinaryResource and other similar classes,
tBinaryResource would be a middle layer between tResource and fTiff with two purposes:
1) organizational structure
2) formats supported internally by SDL, such as BMP and PNG, and thus would use a raw binary read
It would be the default "fallback" resource when the extension cannot be matched. It might actually make sense to merge it with tResource, I haven't given it much thought.
User avatar
joda.bot
Match Winner
Posts: 421
Joined: Sun Jun 20, 2004 11:00 am
Location: Germany
Contact:

Post by joda.bot »

I'm not sure if you all just say "inherit" and actually mean "use" or if you really intend to use inheritance a lot. It might also be that this is faster is C++, but for my Java expierences it's not wise to use inheritance if you intend to just call / use the functions of that class.
From my view tResouceManager is a class and a client application has a single instance of it which is used by throughout the client to load and perhaps save resources. ("use" = having a internal reference to the instance inside the class)

I implemented 1-2 resouce loading systems in Java, but it's a bit easier there, as you already have a mighty IO/URL library.
The concept in Java is to have a URL which is a string more or less (for files, files in zips, internet addresses) and a URLConnection Handler for each protocol (e.g. file://,http:// file:///usr/resource/moviepacks/MoviePack.zip|resouce/cockpit/cockpit.xml)
More Details:
http://java.sun.com/j2se/1.5.0/docs/api ... t/URL.html

ok, my experience with resource loading is:
  • tResourceManager returns a handle (for reading/writing) for a tURL identifier, which points to the spot to load the data from
  • tURL is just a string and allows to do relative path changes etc.
    (e.g. you have "http://localhost/myFolder/file" and "anotherFile" then u can call new tURL("http://localhost/a/myFolder/file", "../anotherFile") to get
    "http://localhost/a/anotherFile"
  • tResouceManager either only loads the data and returns a point to the loaded data
    OR tResourceManager provides an IO handle which can be read or written to (the Methode to read or write to it, might be put into tIOHandle?)
  • tResouceManager should not load, parse or interpret the data,
    it might provide hints from fileextentions or mime types provided by the backend
  • In most cases the class requesting the resource exspects a certain type
    of resouce and can thus call or delegate to the appropriate loader and
    especially for savers.
Problems with this approach:
- std has no URL type/class yet (AFAIK)
- io routines differ alot for each protocol http, ...
Luke-Jr
Dr Z Level
Posts: 2246
Joined: Sun Mar 20, 2005 4:03 pm
Location: IM: [email protected]

Post by Luke-Jr »

joda.bot wrote:I'm not sure if you all just say "inherit" and actually mean "use" or if you really intend to use inheritance a lot.
It'd be somewhat pointless to use anything other than inheritance for subclassing...
joda.bot wrote:It might also be that this is faster is C++, but for my Java expierences it's not wise to use inheritance if you intend to just call / use the functions of that class.
*cough* I wonder why *cough*
joda.bot wrote:From my view tResouceManager is a class and a client application has a single instance of it which is used by throughout the client to load and perhaps save resources. ("use" = having a internal reference to the instance inside the class)
No instances, it just has static functions.
joda.bot wrote:ok, my experience with resource loading is:
[*]tResourceManager returns a handle (for reading/writing) for a tURL identifier, which points to the spot to load the data from
... tResourceManager currently returns a handle for reading (only) for a resource filepath ("AATeam/map-0.2.8.0.dtd"). With my suggested plans, it would return a tResource of some form.
joda.bot wrote:[*]tURL is just a string and allows to do relative path changes etc.
(e.g. you have "http://localhost/myFolder/file" and "anotherFile" then u can call new tURL("http://localhost/a/myFolder/file", "../anotherFile") to get
"http://localhost/a/anotherFile"
Armagetron does not work with URIs (and it would be a waste to code something URL-specific), only resource filepaths. Filepaths by design are never relative.
joda.bot wrote:Problems with this approach:
- std has no URL type/class yet (AFAIK)
Which we don't need. Resource filepaths can be and are represented by tStrings quite nicely.
joda.bot wrote: - io routines differ alot for each protocol http, ...
IO is entirely the internal job of tResourceManager. It doesn't have a plugin interface for the protocol, nor do I see any immediate need for one. If one is desired, it shouldn't be too complicated.
User avatar
wrtlprnft
Reverse Outside Corner Grinder
Posts: 1679
Joined: Wed Jan 04, 2006 4:42 am
Location: 0x08048000
Contact:

Post by wrtlprnft »

I don't like the idea of automatically scanning the resource for the files it needs. The code that scans it has to be on a level where it wouldn't know what type of resource is expected.
The reason why I want this <Files> section is exactly that: you specify you want an image from some source and in the future this image will be referenced not by is path but by an id you specify. This means you can easily change the referenced version by editing the header and the code that actually wants to use the subresource can be sure that it is the type it needs. This means the code requests access to the supresource by something like GetSubResource("id", <type>).
luke wrote:Sounds good, but instead of a static function, wouldn't a constructor make more sense?
This function will call the constructor. Once you tell me how to make a callback to a constructor I might think about it.
I don't know what a std::map is, but would it support the streaming Lucifer mentioned is necessary?
A map is a container, and it would be the job of the objects it contains to support streaming.
I like the registration concept, but I'd make it invisible by having the parent tResource class take care of it for all subclasses...
Well, the code that uses the resource has to know about that subclass. That is, it has to know about the Texture class if it plans to use textures. Creating the appropiate object type is the job of the resource system, of course. So anything that knows how to handle pixel images will automatically get all supported image formats.

I think we should aim for a system like this:
  • tResource
    • tXmlResource (with a parser for the resource tags i mentioned before)
      • cCockpit
      • gMap
      • ...
    • fBinaryResource (provides stuff to read raw files)
      • fTexture (which handles the uncompressed image)
        • fPng
        • fJpeg
        • ...
      • fMusic (provides access to the sound stream)
        • fOgg
        • fMp3
      • ...
So a gMap requesting a texture would just get a fTexture and use it to create a rTexture, not caring about the actual image format...

Gotta go, to be continued later.
There's no place like ::1
User avatar
Z-Man
God & Project Admin
Posts: 11587
Joined: Sun Jan 23, 2005 6:01 pm
Location: Cologne
Contact:

Post by Z-Man »

wrtlprnft wrote:
luke wrote:Sounds good, but instead of a static function, wouldn't a constructor make more sense?
This function will call the constructor. Once you tell me how to make a callback to a constructor I might think about it.
The nNetObjects have a constructor callback; of course, it's only called indirectly and really is a "create an object with a virtual function, register it somewhere, and let the virtual function call the appropriate constructor" thing. Arbitrary callbacks are more flexible. Plus, constructors have limitations in C++, only default and copy constructor get automatically promoted to derived classes, it'd be grand if the same happened for custom constructors. Because of that, I avoid non-default constructors wherever I can and use Init() functions instead.

If you want to go fancy OO, use factories. But really, the difference to callbacks is almost only syntactic and buzzwordiness.

For the real problems discussed, looks like you're on the right way and I'll keep my big mouth shut on account of the too many cooks theorem.
User avatar
Lucifer
Project Developer
Posts: 8640
Joined: Sun Aug 15, 2004 3:32 pm
Location: Republic of Texas
Contact:

Post by Lucifer »

Ok, the resource system needs to be something where new resources can be added with a very minimum amount of work. It also needs to be flexible, since there's no point in adding resources if the resource system itself is going to restrict what types of resources can exist in the first place. We will hit a soft upper bound on useful resource types, hopefully before Bacchus gets out the door. But that won't mean we'll be done creating resource types.

Resources should be able to exist in script. So when we add scripting, we need it to have access to resources, and it needs to be able to create new resources.

A resource consists of several things. There are binary files of arbitrary format, some of which we'll support on our own, some of which we'll expect people that really want them to write scripts to support them. Several examples of binary files are jpg, png, ogg, mpg, wav, flac, tiff. The list goes on.

So we have these xml files that provide all the meta-data needed for the resource system to manage them. That's part of the resource.

In order to give the resource system the lowest overhead for people to use it, it needs to have a simple interface to the rest of the game. I'm envisioning something where you do tResource* theResource = tResourceManager::getResource(myresource); and you get the xml file loaded into memory, you get the binary files all loaded that can be loaded (streamed files are an exception by their nature). Basically after this line, you can send textures from the resource to openGL for rendering with no intermediate steps required.

To support such a simple interface to the rest of the game, the resource system needs to be able to load any arbitrary binary file, and it needs to be able to do it in a generic manner. So you can't have, for example, LoadTiff, LoadJpg, LoadOgg, and so forth for methods. You need one method with one return type that is capable of loading any arbitrary binary file. Obviously that return type will have to be a class pointer, this is a problem solved in the cockpit already that can be copied into the resource system.

Normally, when you need one method with one return type in a bunch of classes you use inheritance to get that method there. So for gMap and gCockpitParser and gTheOthers they'll need to subclass tResource in some way or other, and tResource contains the LoadFile method (or whatever it winds up being called). So what does this LoadFile do?

The simplest way for LoadFile to load any arbitrary format is for it to have a list of sorts, and it takes the file its given (needs a string, not a file pointer, to do it right) and scans its list of loaders to determine which one to use. If none is found, it throws an exception. The loaders themselves should be marked what they're loading for the game and what the format is, for example (texture, tiff), (music, mp3). So if it sees, say, (texture, mp3), it can say "I don't have a loader that loads mp3s as textures". It might say "I have an mp3 loader, but it thinks mp3 is music and I can't use it". The loader itself needs to verify that the file is the right kind and the match has been made and so check the integrity of the file. If it fails, it also needs to throw an exception.

By making this neato LoadFile method work like this, it's possible to implement a new resource with 3 steps:

1. Create the xml file and subclass tXmlParser
2. Create special binary file loaders
3. Register binary file loaders *and* the new subclass created in step 1

Some of us already know from experience that step 1 is hard enough as it is and would like it to be simpler. How simple it will be depends on how much we load up the base classes and how useful the stuff in the base classes will be. But step 1 involves creating a dtd, which by itself isn't hard. The hard part about making a dtd is designing the xml format in the first place.

Step 2 is actually very simple. Just write a function that loads a file and returns a pointer to the data. For known media types, like textures, sound effects, and music, classes that already exist can be used to store the data. The reason for this is simple. Textures will be stored in a known format in memory, and it will be a format that is supported by OpenGL. So the class that contains a texture will need to hold all the information that describes how the data is stored and the data itself, and all texture loaders will be responsible for filling it in. Music and sound are very similar in that regard and so we'll have a strong base class to handle it. I think audio data is probably the easiest format for us to standardize internally. So you're writing a function to a spec, that's all.

Step 3 is the easiest step of all, but may be the one that's hardest to understand to a new guy that doesn't know what he's doing. Documentation is the way to address that.

So after all this, we need tResourceManager to provide a single API that allows the rest of the game to retrieve, create, sort, delete, and do other things to resources. (Mind you, tResourceManager might ultimately find a vacation home as a shared object in a python library for other applications to use in resource creation) tResource needs to provide an interface that makes it possible for tResourceManager to do everything it needs to do, which is moving files around, expiring its cache, and so forth. The subclasses of tResource can extend the interface for their specific components, and they should do so. There's no reason gCockpit should be using a gMap to set itself up, after all. So gCockpitParser would have stuff in its interface that gMap doesn't have, and vice versa. But tResourceManager doesn't know about them, nor does it care for its work. (Except that it needs to be able to create instances of the subclass to give when asked, but it can do that and then dynamic_cast them for internal use. The cockpit/arena/whtever will have to dynamic_cast it back to the subclass for its own use)

That's some part of my thinking behind all of this. There is probably a fair amount of overlap with what wrtlprnft is saying, I don't really know. :) I'm hving a little trouble of my own parsing his first post, not sure why. Some of the details being tossed around aren't fundamentally important to the overall design I'm offering. Yeah, they need to be done one way or another, but how they're done won't affect any of the rest of this stuff.

For example, I really like the elegance of wrtl's <Files> section as far as the code goes. But as an xml author I'd rather not have to do that, I've dealt with writing xml files for vcdimager by hand, which use that convention, and it's a pain. But either way it doesn't affect the design I'm talking about, and I'll take it either way or both because I don't care *that* much.

Also, I object to tResourceManager having its primary access to a file a method that returns a file pointer. The caller needs to know more about the file than a file pointer can give it. This is actually important to the design, and expecting the primary file accessor to get file pointers instead of opening the file itself will significantly restrict the design of the system as a whole.

The next topic is how a resource will be used. So how will it be used? Right now, it's done like this:

Code: Select all

FILE *fp = tResourceManager::getResource(somefilepath);
parser = new gCockpitParser(fp);
parser->Parse();
... or some similar idiom. After this, all you've done is load the xml file and parse it. You haven't loaded any binary files it depends on, like textures and the like. You still have to do that.

I don't like this idiom at all. I think it should be more like this:

Code: Select all

gCockpitParser* theCockpit;
theCockpit = dynamic_cast<gCockpitParser*> tResourceManager::getResource(someresourceindex);
After this piece of code, all binary files for the resource are loaded and can be retrieved through the tResource interface. Actual data structures specific to the cockpit would be retrieved through the interface implemented in gCockpitParser, but they should be initialized already. Maybe that isn't possible and we'll need a third line here to call an initializer, but that's fine. It's still a lot simpler than the first way, which is the way it's done now.

Then there's managing the resource system itself. At various strategic places in the code, more than likely outside the game loop, tResourceManager will need to be called to do things, such as "install this resource", or "expire your cache", or "clear all of your cache". Some of these things will be in response to user input, some of them will be things that are done automatically. Some of them will be in response to command line flags that are designed to allow the game to manage resources in a cronjob. There's a good list of things that have to be done, I'm sure we can brainstorm a pretty complete list, so I'm sure y'all can each come up with at least 1 thing that tResourceManager needs to do that fall under the heading of "resource management".

Finally, it should be obvious at each step where scripting hooks will exist. tResourceManager will need to be available to scripts, scripts should be able to provide file loaders, subclass tXmlResource to make new resource types, and the interfaces created in scripts should be available somehow to the rest of the game to use. tResource's interface, at a bare minimum, must be available to scripts. Like I say, where scripts hook in should be pretty obvious.

See why I tried to keep my first post on the subject short? :) I've got more where this came from, too. Should I move this to the wiki now? We can keep discussing it here and then modifying the document on the wiki. Is this a vision we can all share to at least the extent that I can turn it into a working draft design document instead of a forum post?
Image

Be the devil's own, Lucifer's my name.
- Iron Maiden
User avatar
wrtlprnft
Reverse Outside Corner Grinder
Posts: 1679
Joined: Wed Jan 04, 2006 4:42 am
Location: 0x08048000
Contact:

Post by wrtlprnft »

Lucifer wrote:Resources should be able to exist in script. So when we add scripting, we need it to have access to resources, and it needs to be able to create new resources.
I don't see the trouble with that, we'd just have to offer some kind of function to load the resource. Of course with new types of resources we'd have to extend the script engine as well if needed.
In order to give the resource system the lowest overhead for people to use it, it needs to have a simple interface to the rest of the game. I'm envisioning something where you do tResource* theResource = tResourceManager::getResource(myresource); and you get the xml file loaded into memory, you get the binary files all loaded that can be loaded (streamed files are an exception by their nature). Basically after this line, you can send textures from the resource to openGL for rendering with no intermediate steps required.
I think the binary files should be stored in some object that supports using them as well, like we have it now with rFileTexture: you load the texture with a filename (would now be a resource path) and then it already offers you functions that enable you to render using that file type without dealing with OpenGL at all.
To support such a simple interface to the rest of the game, the resource system needs to be able to load any arbitrary binary file, and it needs to be able to do it in a generic manner. So you can't have, for example, LoadTiff, LoadJpg, LoadOgg, and so forth for methods. You need one method with one return type that is capable of loading any arbitrary binary file. Obviously that return type will have to be a class pointer, this is a problem solved in the cockpit already that can be copied into the resource system.
I solved that already? Where?
As I already outlined in my last post that function would return a fBinaryResource pointer, but if the stuff that uses it already knows that that file is a texture (ie, it requested a texture) it can dynamic_cast it to a fTexture which will not only provide access to the uncompressed binary data but only provide functions to use that texture with OpenGL.
The simplest way for LoadFile to load any arbitrary format is for it to have a list of sorts, and it takes the file its given (needs a string, not a file pointer, to do it right) and scans its list of loaders to determine which one to use. If none is found, it throws an exception. The loaders themselves should be marked what they're loading for the game and what the format is, for example (texture, tiff), (music, mp3). So if it sees, say, (texture, mp3), it can say "I don't have a loader that loads mp3s as textures". It might say "I have an mp3 loader, but it thinks mp3 is music and I can't use it". The loader itself needs to verify that the file is the right kind and the match has been made and so check the integrity of the file. If it fails, it also needs to throw an exception.
That LoadFile would use some list that is actually filled by higher parts of the code, it would use that list to create a new object of the right type and would then pass on to that object. This object would be derived from tBinaryResouce which would offer funtions to locate and open the file and return the resulting file pointer. The object would then try to load the file from that file pointer and store it to make it available.
By making this neato LoadFile method work like this, it's possible to implement a new resource with 3 steps:

1. Create the xml file and subclass tXmlParser
2. Create special binary file loaders
3. Register binary file loaders *and* the new subclass created in step 1
That makes sense to me, although most classes should not use step 2 since they'll just use generic stuff like textures and models.
Step 3 is the easiest step of all, but may be the one that's hardest to understand to a new guy that doesn't know what he's doing. Documentation is the way to address that.
I imagine it being as hard as creating a new config item: You provide that callback funtion making a new object (ideally a static function within the object you want to create) and then create a static global object with a nonsense- name whose sole purpose is it to have a constructor that registers the object at global variable creation time.
That's some part of my thinking behind all of this. There is probably a fair amount of overlap with what wrtlprnft is saying, I don't really know. :) I'm hving a little trouble of my own parsing his first post, not sure why. Some of the details being tossed around aren't fundamentally important to the overall design I'm offering. Yeah, they need to be done one way or another, but how they're done won't affect any of the rest of this stuff.
Yeah, I like to go into details... I'm always looking for a simple, realistic solution, so I wanna make sure I know how to do some of the things easily first and then look what I'll do based on that.
For example, I really like the elegance of wrtl's <Files> section as far as the code goes. But as an xml author I'd rather not have to do that, I've dealt with writing xml files for vcdimager by hand, which use that convention, and it's a pain. But either way it doesn't affect the design I'm talking about, and I'll take it either way or both because I don't care *that* much.
Lots of things are a pain for the resource author, but on the other hand they make the whole system more flexible. That removes the fixing off attribute names later, so you can actually say <File type="texture" src="xyz.png" id="myskin" /> ... <Rectangle skin="myskin" /> without having the resouce manager know that skin is an attribute that wants to load a binary file that has to be a texture.
Also, I object to tResourceManager having its primary access to a file a method that returns a file pointer. The caller needs to know more about the file than a file pointer can give it. This is actually important to the design, and expecting the primary file accessor to get file pointers instead of opening the file itself will significantly restrict the design of the system as a whole.
You get me wrong there. The file pointer won't leak out to the highlevel code in the end, but it will be provided to the class that's responsible to parse that format.
The next topic is how a resource will be used. So how will it be used? Right now, it's done like this:

Code: Select all

FILE *fp = tResourceManager::getResource(somefilepath);
parser = new gCockpitParser(fp);
parser->Parse();
... or some similar idiom. After this, all you've done is load the xml file and parse it. You haven't loaded any binary files it depends on, like textures and the like. You still have to do that.
Is it? As far as I know the cockpit handes that by itself, so the exact way to parse the resouce is to be decided yet.
I don't like this idiom at all. I think it should be more like this:

Code: Select all

gCockpitParser* theCockpit;
theCockpit = dynamic_cast<gCockpitParser*> tResourceManager::getResource(someresourceindex);
After this piece of code, all binary files for the resource are loaded and can be retrieved through the tResource interface. Actual data structures specific to the cockpit would be retrieved through the interface implemented in gCockpitParser, but they should be initialized already. Maybe that isn't possible and we'll need a third line here to call an initializer, but that's fine. It's still a lot simpler than the first way, which is the way it's done now.
Again, it's not done like that right now, at least not that I know of. Actually the cockpit right now is designed in a way that the same structure that cares about parsing also holds the parsed stuff and renders it.
See why I tried to keep my first post on the subject short? :) I've got more where this came from, too. Should I move this to the wiki now? We can keep discussing it here and then modifying the document on the wiki. Is this a vision we can all share to at least the extent that I can turn it into a working draft design document instead of a forum post?
"short". Well, this should really move into the wiki. If we have the stuff in a format that allows everyone to put in decided changes this is way better than having a long disctussion about all the subjects here, because we actually get an outcome and it's easy to get a summary what has been decided.
That's what I don't like about some of the threads in here, they grow forever with small things and in the end someone who didn't keep track of them has no chance to fish out the important things without reading everyting.
z-man wrote:For the real problems discussed, looks like you're on the right way and I'll keep my big mouth shut on account of the too many cooks theorem.
As far as I know you're the most experienced c++ cook here so I'd always value your opinion here :)
There's no place like ::1
Luke-Jr
Dr Z Level
Posts: 2246
Joined: Sun Mar 20, 2005 4:03 pm
Location: IM: [email protected]

Post by Luke-Jr »

wrtlprnft wrote:I don't like the idea of automatically scanning the resource for the files it needs. The code that scans it has to be on a level where it wouldn't know what type of resource is expected.
It doesn't need to know. It just looks for 'datasrc' or 'src' attributes on any element that it is parsing for a higher-level interface (such as gMap).
wrtlprnft wrote:
luke wrote:Sounds good, but instead of a static function, wouldn't a constructor make more sense?
This function will call the constructor. Once you tell me how to make a callback to a constructor I might think about it.
No need for a callback... just use the class itself ;)
wrtlprnft wrote:
I like the registration concept, but I'd make it invisible by having the parent tResource class take care of it for all subclasses...
Well, the code that uses the resource has to know about that subclass. That is, it has to know about the Texture class if it plans to use textures. Creating the appropiate object type is the job of the resource system, of course. So anything that knows how to handle pixel images will automatically get all supported image formats.
What "Texture class"? The code using textures merely needs to do something like ThisWall.texture.read().
wrtlprnft wrote:So a gMap requesting a texture would just get a fTexture and use it to create a rTexture, not caring about the actual image format...
gMap will never need to request anything. It will simply see the XML attribute has a tResource value and read from that. tXmlResource will take care of that.
User avatar
Lucifer
Project Developer
Posts: 8640
Joined: Sun Aug 15, 2004 3:32 pm
Location: Republic of Texas
Contact:

Post by Lucifer »

Ok, I'll take it to the wiki, but we should probably keep discussion here, at least until I get postfix fixed on the wiki so it can send mail notifications again.

Um, I disagree that the class that holds the texture data should also know how to render it. There's some ongoing discussion and a lot of threads talking about factoring all the rendering code into one place, and there's a standing agreement that we will do that as we go, essentially. Nobody's yet put forth a complete vision on where the rendering engine will go, I think we're all still thinking about it, and always on the edge of that discussion are two other things: Do we switch out to something like OGRE, and what about the GUI?

So, sure, we can have a rTexture class and all that stuff, but it should only use a pointer at most to the texture loaded from the resource system, it should never own any of this data. IMO. There is an argument to be made that a weak fence should exist between the renderer and the resource system, but I still favor a strong wall instead. Part of factoring the rendering engine is creating a central flow of data from the game to the renderer, and the flow is one-way, i.e. data doesn't come back from the renderer because the renderer is output, not input. As long as the user has control over that data, then it needs to be owned on this side of the wall from the renderer, not the side the renderer is on.
Image

Be the devil's own, Lucifer's my name.
- Iron Maiden
User avatar
wrtlprnft
Reverse Outside Corner Grinder
Posts: 1679
Joined: Wed Jan 04, 2006 4:42 am
Location: 0x08048000
Contact:

Post by wrtlprnft »

Luke-Jr wrote:
wrtlprnft wrote:I don't like the idea of automatically scanning the resource for the files it needs. The code that scans it has to be on a level where it wouldn't know what type of resource is expected.
It doesn't need to know. It just looks for 'datasrc' or 'src' attributes on any element that it is parsing for a higher-level interface (such as gMap).
Yes it does. There's no point in seeing a src attribute that points to a music file if the resource actually needs a texture. This would mean that whereever the texture is used it would have to perform a check if it is actually the right resource type. The correct place to check is where the resource gets loaded: If that file is supposed to be a texture and it really is a music file, there is no point in even loading it since it's unusuable anyways. This is the right point to generate an error message and refuse to load the resource.
luke wrote:
wrtlprnft wrote:
luke wrote:Sounds good, but instead of a static function, wouldn't a constructor make more sense?
This function will call the constructor. Once you tell me how to make a callback to a constructor I might think about it.
No need for a callback... just use the class itself ;)
How? You need an object for every file you want to load, so you need a way to create a new class of the right instance.
luke wrote:
wrtlprnft wrote:
I like the registration concept, but I'd make it invisible by having the parent tResource class take care of it for all subclasses...
Well, the code that uses the resource has to know about that subclass. That is, it has to know about the Texture class if it plans to use textures. Creating the appropiate object type is the job of the resource system, of course. So anything that knows how to handle pixel images will automatically get all supported image formats.
What "Texture class"? The code using textures merely needs to do something like ThisWall.texture.read().
? What is ThisWall in this case and why does it have a texture member? I don't understand that. This is where we want to link the texture in the end, but requesting that object from the resource manager takes place somewhere else.
luke wrote:
wrtlprnft wrote:So a gMap requesting a texture would just get a fTexture and use it to create a rTexture, not caring about the actual image format...
gMap will never need to request anything. It will simply see the XML attribute has a tResource value and read from that. tXmlResource will take care of that.
It will see from the context that this xml node has an atrribute that's supposed to refer to a resource, so it'll pass that id and the expected type (texture, music, model etc) to some function in the resource manager that returns a pointer to the already loaded resource. Then it'll convert the pointer to the type of resource it needs and gain access to the resource object's actual data in some kind of format that makes sense. An image would have width and height information associated with it that doesn't make sense for a music file, so we can't just make generic variables that are in the base class. We have to convert.
Lucifer wrote:Um, I disagree that the class that holds the texture data should also know how to render it. There's some ongoing discussion and a lot of threads talking about factoring all the rendering code into one place, and there's a standing agreement that we will do that as we go, essentially. Nobody's yet put forth a complete vision on where the rendering engine will go, I think we're all still thinking about it, and always on the edge of that discussion are two other things: Do we switch out to something like OGRE, and what about the GUI?
Sure, things like the GUI shouldn't be rendered by the same class that parses them.
The texture shouldn't be rendered inside the data class, but there should be a function that loads it into OpenGL so you can render something using it. This way we can change the format that the texture is stored internally and the higher level code won't really notice anything about it.
Luci wrote:So, sure, we can have a rTexture class and all that stuff, but it should only use a pointer at most to the texture loaded from the resource system, it should never own any of this data. IMO. There is an argument to be made that a weak fence should exist between the renderer and the resource system, but I still favor a strong wall instead. Part of factoring the rendering engine is creating a central flow of data from the game to the renderer, and the flow is one-way, i.e. data doesn't come back from the renderer because the renderer is output, not input. As long as the user has control over that data, then it needs to be owned on this side of the wall from the renderer, not the side the renderer is on.
I think the data should be owned by the resouce manager, not by the code that's using it. There would be a function UnloadResouce() that would destroy all data that got loaded from that resource, assuming it's not used anymore (for example the user left the grid, so the map isn't needed anymore).
There's no place like ::1
User avatar
Lucifer
Project Developer
Posts: 8640
Joined: Sun Aug 15, 2004 3:32 pm
Location: Republic of Texas
Contact:

Post by Lucifer »

Right, the resource manager is on the user side of the rendering fence. It's like this:

Everything else | Renderer

Data flows left to right. :)

I don't think the resource class for textures should even support openGL. Consider several scenarios:

We pick OGRE, and (I'm making this part up) it loads its own textures, but it supports getting binary data from memory. So in the game loop, we have to push texture information to OGRE to be rendered. OGRE needs direct access to the data, we can't just give it to openGL and hope OGRE can deal with it.

We keep brewing our own. Right now, if you hadn't noticed, if you use a semi-transparent cycle wall texture, it's not always rendered as such. It depends entirely on what order the walls get laid down. z-man suggested that a dedicated renderer (which we do not have but intend to work towards) would be able to sort all textures and objects in the order in which they should be rendered, and that this would fix this bug among others. To do that, the renderer needs access to all objects at once (in order to sort them) and knowledge of what textures they contain. Technically, the resource class holding the texture *could* pass it to openGL in this scenario, however:

We decide to support multiple rendering backends. This is a direction several of us are very interested in going. Imagine rendering movies of the game using pov-ray! Or any other arbitrary rendering engine, for that matter. We could give users choices of what to use, which will change the dependencies needed, might even make dependencies more reasonable for some users. In this case, even with our own renderer (which some of us want to have anyway because its fun to work on), we still need to push all openGL calls into the renderer, because for a renderer like pov-ray, we may not even be able to use openGL calls. We might well need to render it in a totally different way!

So, no openGL code on *this* side of the wall. Rather, no new openGL code, and as we break down old classes and shift them around we should be pushing all openGL code to *that* side of the wall. The resource system and how it handles textures and coding moviepacks is an excellent opportunity to push a lot of the rendering code to the other side of the wall, and we should take advantage of it to do that. So, we take baby steps while we iron out the resource system and get existing code using it the way we want it to be used, but we're not really done until the classes and other code we break down to setup the resource system has pushed openGL code to the other side of the wall.

I'll see if I can dig up some of the threads on it for your perusal, now that I think about it, most of the renderer factoring project was discussed before you came along, so it's highly likely you just missed it.
Image

Be the devil's own, Lucifer's my name.
- Iron Maiden
Post Reply