Packaging practicalities

I’ve organized most of the various essential resources needed by Doomsday into a set of packages. This is an important step in verifying that the new package system is working as intended.

  • net.dengine.stdlib: Doomsday Script standard library; a collection of script modules for configuration and general purposes.
  • net.dengine.stdlib.gui: Graphics-related script modules for the DS standard library. Only needed by graphical apps like the Doomsday Client.
  • net.dengine.base: Common basic resources for the Doomsday apps, e.g., help strings and flag definitions.
  • net.dengine.client: Client-specific resources/modules, e.g., script for determining the default values for client/GUI configuration.
  • net.dengine.client.defaultstyle: Images, metrics, fonts, etc. for the default UI style of the client.
  • net.dengine.client.renderer: General purpose shaders, bitmaps, etc. for the renderer.
  • net.dengine.client.renderer.lensflares: Lens flare feature package: shaders and textures to be used when rendering GL2 lens flares.

I’ve identified the following use cases related to packages:

Nested packages. When dealing with a large hierarchy of packages, it is nice to have some assistance that avoids redundancy. The package loader applies a special rule to a .pack folder whose parent folder is also a .pack folder. The package identifier for such a nested package is formed by treating the parent’s identifier as a prefix.

Consider the following folder structure:

net.dengine.client.pack/
     defaultstyle.pack/
     renderer.pack/
         lensflares.pack/

The resulting package identifiers are:

net.dengine.client
net.dengine.client.defaultstyle
net.dengine.client.renderer
net.dengine.client.renderer.lensflares

Nesting does not imply automatic loading of the contained packages. If such a feature is needed, it could be done via metadata in the parent package: “also load packages X, Y, Z when this package is loaded.”

Locating a specific file in a specific package. Since packages are folders in FS2, one can simply ask for the root folder of the package and locate files as usual:

Help_ReadStrings(App::packageLoader().package("net.dengine.base")
                 .root().locate<File>("helpstrings.txt"));

Packages for subsystems. If there is a package corresponding an engine subsystem, such as the renderer, the package is loaded when the subsystem is initialized. This is currently done with the renderer packages.

Init packages. The applications expect that a set of essential core resources are loaded immediately during startup. This includes things like the DS standard library, and the application’s own primary package (net.dengine.client, for example). Because this includes things that must be present from the beginning (for instance the config script might need DS stdlib), the loading of these packages is handled by de::App during its initialization. One simply needs to let de::App know the list of init packages before calling initSubsystems(). While de::App itself adds the DS stdlib package, classes like de::GuiApp automatically make sure their required packages are listed.

Feature packages. There may be several alternative packages that provide assets for a feature of the engine. For this purpose, a package may specify an alias that appears under /packs in addition to the unique identifier of the package. This ensures only one package provides the feature at a time, and provides access to the package contents at a fixed location in the file system (for instance /packs/feature.lensflares).

It would be possible to use the same aliasing mechanism for all assets: one could for instance specify the alias “asset.model.thing.player” to identify a model for a mobj with type “player”. I’m a bit on the fence with how this would play out, though. On one hand, it neatly handles conflict/priority resolution and enforces a logical and uniform asset identification system. There is an easy answer to the question “is there an asset for purpose X?”: check /packs/asset.X. However, some care must be taken so that a single package can provide multiple assets. Most likely the package’s metadata needs to declare the assets separately. This actually provides a very neat location for model definitions, in the metadata of the package. There is no need to collect all of it in a central repository, and the act of loading the package makes the definitions available for the renderer. One can even observe when packages of a certain pattern (asset.model.*) are loaded and perform resource management actions accordingly.

I believe I will try this out as my first attempt to see how the new model packages would work.

DS modules in a package. A package may make DS modules available to be imported while the package is loaded. In practice, the module folders are listed in the importPath variable in the package metadata. This is used by the stdlib packages, however any package can provide modules.

importPath <modules>

(Note the Info array notation with angle brackets.)

Filtering/sorting file searches. In cases where many packages provide certain types of files, for instance GL shaders, it is necessary to find all appropriate files in all loaded packages. However, since package order is significant (later packages may override earlier ones), when finding files in this scenario the results must be sorted in the package load order. PackageLoader now provides a method for sorting files based on package order.

Plugin packages. Each plugin will be contained in a package. This solves the issue of making libcore able to load plugins: just load the package via PackageLoader. Plugin packages are .packs with the plugin binary and any additional files and metadata. libcore will load the plugins and trigger all load/unload actions as defined in the package. However, since the game plugins still rely heavily on FS1 and the old-style resource management, it’s a bit premature to switch all plugins to new format .packs at this time.