OSGi is an extremely powerful module system for the Java virtual machine. Code and resources are packaged into bundles that can be installed into a running OSGi container for use.
Using bundles to deliver compiled Java code is essentially a solved
problem. Bundles specify what Java packages
they export and import. If a package is exported, then that
package can be imported by other bundles. The OSGi resolver
is responsible for wiring bundles together. For example, if
a bundle B0
specifies that it imports a package named P0
,
and another bundle B1
specifies that it exports a package named
P0
, then the OSGi runtime will create a wire W0
from B0
to B1
.
Whenever code in B0
references a class in
P0
, then that class will be loaded from B1
by traversing W0
.
This creates a directed acyclic graph
where the vertices of the graph are packages and the edges of the
graph are wires.
We can look at this in more general terms: We can think of the above
in terms of requirements that can be satisfied by capabilities.
For example, we can think of a package import as being a specific
requirement: A package p
attempting to import a particular package q
can be thought of as a requirement for q
by p
. Conversely, a
package export can be thought of as a capability: An export
of a package p
can be thought of as a capability to satisfy a
requirement for p
.
In these terms, then, the OSGi runtime is essentially a constraint solving system that takes a set of requirements and capabilities as input, and tries to find a solution that satisfies all of the requirements using the provided capabilities.
So what's the point of explaining all of this? Well, beyond importing and exporting Java code, the OSGi system actually allows developers to declare their own types of capabilities and requirements that will be solved by the OSGi runtime when the bundles specifying them are installed. This allows for bundles that contain things other than Java classes to get the same strong versioning and dependency handling that Java code enjoys.
In my case, I'm using this to get versioning and dependency handling for game engine assets. A bundle in the system I've put together can place declarations in the manifest such as:
Provide-Capability: com.io7m.callisto.resources.package; name = a.b.c; version = 1.0
That is, the bundle states that it provides a package called a.b.c
, version 1.0
,
containing resources, in the category of requirements called
com.io7m.callisto.resources.package
. Another bundle declares:
Require-Capability: com.io7m.callisto.resources.package; filter=(& (name = a.b.c) (version >= 1.0) (version < 2.0))
That is, the bundle states that it requires a package that has the
name a.b.c
AND has a version greater than or equal to 1.0
AND has
a version less than 2.0
(so for example, 1.1
would satisfy the
requirement, but 2.1
would not). The requirement is only satisfied
when all three constraints are met.
Those bundles will be wired together when they're installed
and a bundle can only access the resources of another bundle via
the created wire when it explicitly imports that bundle via a
Require-Capability
declaration. I get all of the benefits of
strong versioning, dependency handling, proper cross-bundle resource
visibility, good error messages when dependencies aren't met, etc,
essentially for free. If a user wants to install a bundle containing
resources, the declared capabilities and requirements of the
package mean that it's trivial to automatically fetch dependencies
of the bundles without the user having to do anything.
I don't know of any modern game engines that use a system like this. Apart from anything, it's phenomenally impractical to build a system like this outside of a virtual machine due to the constraints imposed by working with native code directly. Game developers are religiously opposed to anything that isn't C++ and so refuse to consider the alternatives. Generally, in most games, all of the resources are stuffed into one giant flat namespace without any sort of versioning, privacy control, dependency handling, or anything else. The result is immediate dependency hell as soon as third parties try to produce modifications for the games. Because there's no dependency information, installing modifications for games must be done manually, and there's absolutely no way to ensure that arbitrary modifications are compatible with each other. Worse, when modifications are incompatible, the result will be obscure problems and crashes at runtime instead of actionable "Modification X is incompatible with modification Y" error messages.
In life never do as others do...
I actually asked a while back on Stack Overflow if anyone had any idea how to solve the problem I've been attempting to solve with the room model.
Given that I've now actually solved it, I went back to append the
answer to my question. I immediately ran into an endless series of
idiotically draconian checks that essentially wouldn't let me post
the answer to my own question. I eventually gave up trying to get the
post to pass the full body cavity search and blood sample analysis,
so ended up wrapping the entire thing in preformatted text
tags
and preceding it with a plea that a passing editor fix it. Consider
me discouraged from ever bothering to post on Stack Overflow again.
For future reference, here's the solution I posted:
I came up with a solution to this. In order to solve this efficiently, some sort of spatial data structure is needed in order to query which polygons are overlapped by a given rectangular area. I used a Quadtree. It's also necessary for the polygon data structure being used to be able to distinguish between internal and external edges. An edge is internal if it is common to two polygons.
The steps are as follows (assuming a coordinate system with the origin in the top-left corner):
Insert all polygons into whatever spatial data structure you're using.
Iterate over all polygons and build a list of all of the Y values upon which vertices occur. This has the effect of conceptually dividing up the scene into horizontal strips:
Iterate over the pairs of Y values from top to bottom. For each
pair (y0, y1)
of Y values, declare a rectangular area a
with
the the top left corner (0, y0)
and bottom right corner (width, y1)
. Determine the set of polygons S
that are overlapped by a
by querying the spatial data structure. For each polygon p
in S
,
determine the set of edges E
of p
that are overlapped by a
. For
best results, ignore any edge in E
with a normal that points
directly up or down. For each edge e
in E
, it's then necessary to
determine the pair of points at which e
intersects the top and bottom
edges of a
. This is achieved with a simple line intersection
test, treating the top and bottom edges of a
as simple horizontal
line segments. Join the intersection points to create a set of new
line segments, shown in red:
Create vertical line segments L0 = (0, y0) → (0, y1)
and L1 = (width, y0) → (width, y1)
. Working from left to right, gather any
line segments created in the preceding step into pairs, ignoring any
line segments that were created from internal edges. If there were
no intersecting external edges, then the only two edges will be L0
and L1
. In this example strip, only four edges remain:
Join the vertices in the remaining pairs of edges to create polygons:
Repeating the above process for each horizontal strip achieves the desired result. Assuming a set of convex, non-overlapping polygons as input, the created polygons are guaranteed to be either triangles or quadrilaterals. If a horizontal strip contains no edges, the algorithm will create a single rectangle. If no polygons exist in the scene, the algorithm will create a single rectangle covering the whole scene.
Good progress made on the room model.
The algorithm now correctly breaks up the space into horizontal spans, and produces a graph of cells that each have links to the cells above and below. I believe this should be enough information to semi-realistically propagate water through the space simply by walking up and down the graph of nodes.
Been working intensely on the room model.
The intention here is to analyze a set of polygons and divide the space outside the polygons into horizontal spans. The horizontal spans represent areas within which water would pool if it was poured into the space. Actually producing these polygons with usable up/down connectivity information has turned out to be a surprisingly fiddly computational geometry problem! I've still not completely solved it.
Experimenting with a room model for the engine.