Engine Interface Design Poll

Started by
10 comments, last by Emmanuel Deloget 16 years, 10 months ago
Hi all, I am building a small engine from scratch. I have already followed a couple examples, "RPG Programming with DirectX" - small 3d engine "Enginuity" - the backbone of an engine And I have worked quite a bit with the Irrlicht Graphics engine. My question involves the programmers interface with the engine. The difference approaches I have seen are: In the RPG and Enginuity engines, the game programmer (using the engine) can instantiate various elements of the engine independently, and often will have to. The Irrlicht engine uses a central "IrrlichtDevice" object. Then, every engine element is accessed through the device. The programmer cannot instantiate other elements of the engine directly. This helps prevent memory leaks, and creates a central location to access all of the engine. Which designs have you worked with? Which design would you prefer? Why? Thanks :) -Sevans
-Sevans
Advertisement
Central that the user access components through to maximize the abstraction of the lower levels.
My engine usually consist in distinct classes which perform complementary tasks. Some of these are tightly correlated, while others aren't. The game instantiates them wherever necessary.

Central object or small set of objects, which often expose other subsystems through them. You don't get to instantiate any of the engine's objects, ever.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
The engines I have created have had a few different forms, but the one that I've been sticking with for the last several projects [as I find it works best] is very heavy on aggrigation as a means of enabling extra features. Lots of objects from my engine are instantiated by the user of the engine, but very little is actually done with these objects by the engine's user. Major engine components [like graphics, physics, navigation, etc] are all handled behind the scenes, updated in the background instead of the the actual engine user.

I'm personally a big fan of threads too, and use them heavily.

I'm not saying this is the best way to do it, but I find it to be very easy to work with [even if occationally inefficient in certain instances, especially with regard to memory, as lots and lots of pointers are held which could be phased out if i changed the design in such a way as to remove thier need]. It takes quite a bit of effort to get it up-and-running [actually making the engine], but working with it is a breeze.

With one exception, I'm the only one who has ever used the engines I've made though, so I can't say if they are easy to pick up and learn really....
[My] engine has two general interfaces. One determins how the engine
builds, the other determins accessibility to the engine.

The engine allows the creation of multiple (or no) renderers, input,
and audio SubSystems which are accessable from the Engine Core. This
provides a) A Central location to access engine components, and
b) Extensibility, as the developer can plug (or create) there own drivers for
more specific renderers, audio SubSystems, et al..

If these systems are not specified, but are attempted execution,
it is handled by the engines HEL, which handles it through software.

The engine also uses a set of defined constants the developer defines,
to determin how the engine builds. This lets the engine know what specific
OS to build for, and the architecture. If no OS is specified, but an
architecture is given, the engines HEL renderes everything to buffer,
and the HAL uses hardware level access, if possible. If not, the
developer needs to handle the buffers generated by the HEL.
Thank you for the replies everyone. They were all helpful in their own right.

I have chosen to use a 'single device' interface. Since most of my engine consists of Singleton's the components could be accessed individually, but it is not necessary.

Thanks again,

Sevans
-Sevans
Well, there is two different part in an engine - the graphic resource management and the world management. I will speak only about the graphic resource management - as the world management seems pretty obvious to me (I don't like scene graphs that much).

I kinda love building facades1 - not that it's really rewarding, but once it's build, you can watch your work and say "wow, it's even cleaner than the API I encapsulated". Not to mention that it allows me to experiment with software design decisions, which is kinda cool since software design is the basis of my job.

My latest experiment was with DirectX (although I had many other things to do, and I lost contact with what I was doing). First thing I noted: DirectX encapsulate a device, but that's really not what you want to work with. You want to work with a renderer - and that's quite different. It's not the job of a renderer to create and manage resources.

The interface to the renderer is quite simple: it should allow you to set up the state and to initiate a rendering operation. So that leaves us with many functions of the D3D device that shall be implemented somewhere else. Among these, the resource creation functions.

So, should I implement some kind of resource (who said manager? no, no manager) factory, or should I allow the user to allocate his own resources? Also known as: should I do texture = resource_factory.create_texture(...) or texture = new dx9::texture(...)?

The first solution has an obvious advantage - I have a total control on what the programmer is doing. I can spot leaks and operate on the resources without telling it to the programmer (for example, in order to recover from a device_lost scenario). The problem is that I must consider all the possible resource creation scenarii - I don't want my programmer to be limited by what I allow him to do (that's the Irrlicht path BTW) - and he cannot extends my framework easily, because he would have to modify the source code to do so. Furthermore, it doesn't allow the programmer to handle the memory in his own way. What if he implemented a specific allocation scheme for his game?

The other solution solves this two issues - but leaves us with the internal resource management problem. I mean, I don't want the programmer to explicitly deal with the device_lost issue (for example) - that would be kind of silly to implement a facade that doesn't care about the major DX issue. There is also the problem of resource leaks, which is quite nasty.

My solution to this problem was quite a simple one - it implied the creation of a resource (who said manager again? what's your problem with resource managers?) list object - which is responsible for holding a list of active resources. Once a resource object has been created, it is tied to the resource_list (or whatever you call it). When the programmer don't need the resource anymore, he unties it.

We only dealt with one part of the problem, right? When you create resources in DirectX, you need to have access to a IDirect3D9Device interface. Should I pass this to my resource constructor? Hell, no! What would be the purpose of an encapsulation if the main encapsulated interface was freely available?

Every different resource class (vertex_buffer, texture_2d, and so on) inherit the resource. This class defines a private method resource::activate(IDirect3D9Device*), which creates the real D3D resource.

To give you a pseudocode example of how it looks:
  // usage code  // dx9::texture tmap(L"path_to_some_image");  // dx9::resource_list& rslist = device.get_resource_list();  // rslist.tie(&texture);  void dx9::resource_list::tie(dx9::resource* res)  {    if (!res) throw dx9::exception_invalid_parameter("res", res);    m_resources.push_back(res);    res->active(m_d3d_device);  }

Of course, resource_list is a friend of resource.

What's interesting here is that now, I allow the user to inherit his own classes from resource. All he have to do is to implement the logic in the activate() method.

Of course, there is still a weakness: the framework can't be extended by the programmer without giving him access to the IDirect3D9Device interface. I'm not sure there is a way to overcome that situation. At least, he doesn't have to modify the framework itself, and this is a good point.

And now, for something different: this is a UML diagram of some classes I presented here. You'll see a device class on the top - that's the class which is used to create the renderer and the resource_list. It doesn't expose much methods (create(), destroy(), change_resolution() and so on), so you should not view it as an encapsulation of the D3D device interface.



Now, comment! [smile]


1: for those who are not really pattern-oriented (which is not that much a bad thing), a facade is a complete encapsulation of a third party component (to present it using simple words). If you want to learn more about it, I think that google might help you.
Wow, thanks for the awesome posting.

First off, let me say that I love facadism (its use in Irrlicht keeps me coming back) and would love to incorporate it into my engines design.

Second, let me point out that the managers in my design below are not factories. They only manipulate objects.

I believe I understand your resource management system. I already have something similar in my design (see below). Except I use a root object class which uses reference counting to manage objects of itself in a list. The MemoryManager then provides some functionality in dealing with the allocated resources, like freeing them. The memory manager class is definitely not necessary, but provides a nice interface for dealing with the objects. And I have toyed with the idea of making it a task that is runnable by the Kernel.

I have not yet started working on the visual portion of the engine, it is up next. What I have (minus lesser important parts) can be seen below. It is not a perfect diagram and lacks a lot of implementation detail, but who cares about implementation during design :). I have already coded what is below and all works well.

Crude Engine UML

If you have some feedback about my design, post it already, jeez ^.^d

Now that I have the engine interface decided, only one thing remains bothersome about my design. Currently, if the application would like to have some custom task done every frame, there must be a class that extends ITask and implements the update method with logic to preform said task. Then the user must add the task to the Kernel (this could be switched to use a factory later but w/e for now). I have been toying with the idea of switching ITask to a struct, lets call it sProcess, that would hold a pointer to a function. The process would be added to the kernel. The function would then be called each time the kernel decides to process the task. This would require the use of either functors or a bunch of static method calls (eww). At the moment I have nothing pushing me one way or the other. Can anyone see any problems with either choice?

Thanks again,

-Sevans
-Sevans
Quote:Original post by Sevans
Wow, thanks for the awesome posting.

First off, let me say that I love facadism (its use in Irrlicht keeps me coming back) and would love to incorporate it into my engines design.

Second, let me point out that the managers in my design below are not factories. They only manipulate objects.

You have to be clear about one point: a class should have only one responsibility - i.e. it should have only one reason to be modified (for the record, that's the Single Responsibility Principle (SRP; google for more), one of the 5 main object oriented design principle). If your class does two different things, there are two different areas for changes, and it has two responsibilities. Managers are supposed to have many responsibilities - if they don't then they aren't managers and you'd better find a class name which is more suitable. If they do, then you break the SRP and this will have unexpected consequences on your design - most of the time, it introduces some viscosity in your code base (viscosity is a phenomenon that can be described as: if you have to introduce a modification, it's easier to do a small, ugly hack in the code than to implement the modification correctly).

For example, your log manager is not a log manager. It's a logger. If your memory manager is a manager, and it should be broken into several objects (for example, a memory pool, an instance tracker and a factory (if it create things)). If it's not, then it's probably a memory pool - so call it like this.

Quote:I believe I understand your resource management system.

I hope so! it's quite difficult to come with something easier to understand! [smile] (at least for me; simplicity is something which is quite hard to achieve. Like Pascal said, "I would have made this shorter, but I lacked the time to do so").

Quote:I already have something similar in my design (see below). Except I use a root object class which uses reference counting to manage objects of itself in a list. The MemoryManager then provides some functionality in dealing with the allocated resources, like freeing them. The memory manager class is definitely not necessary, but provides a nice interface for dealing with the objects. And I have toyed with the idea of making it a task that is runnable by the Kernel.

Well, design-wise it would be better to create a task that references the memory pool/manager/whatever than to transform the memory "thing" into a task. But you are surely doing something like that anyway.

Quote:I have not yet started working on the visual portion of the engine, it is up next. What I have (minus lesser important parts) can be seen below. It is not a perfect diagram and lacks a lot of implementation detail, but who cares about implementation during design :). I have already coded what is below and all works well.

Crude Engine UML

If you have some feedback about my design, post it already, jeez ^.^d

I do.

First, there are a lot of singletons. This might not be bad (I consider this is bad, but I don't like singletons). Let's not discuss about the pro and cons of singletons here, there has been many thread on this subject in the recent past.

What's bad, however, is the fact that a singleton is holding references to other singletons. The Device class might be a singleton. It might hold a logger, a memory "thing", a kernel and a settings "thing" (settings collection?). But then, why would these three classes be singletons? If they are created then hold by a singleton, you don't need to make them unique - they are unique by default. Just make sure that you can't construct/destruct them out of the Device class and you'll be safe.

It also seems that your device class is roughly a god class - it does whatever papa and mama needs to do, which is a major issue.

You should consider using the law of demeter when you design a system (shameless plug: first part of the English version of my "law of demeter" blog post; yes, I have English posts on my French speaking blog). This link gives a definition of the law, although it doesn't explain exactly what it means code-wise.

Concretely, the law forbids you to write something like:
void f(B b){A a;a = b.get_a(); // accessor; the a instance is not created in "get_a()"a.do_something();}

And forces you to write it like this:
void f(B b){  b.do_something_with_a();}

(Of course, the function names are horrible).
If the first code has some meaning, then the second code definitely have to (and is a lot cleaner). If B::do_something_with_a() is meaningless, then it means that B shall not contain a A instance.

What is the consequence of this law on your device class? That's actually quite simple - let's take the Kernel example: if device.add_tack_to_kernel(task) makes sense, then it should be implemented this way - and you don't need any accessor to get() the kernel. If it does not, then the kernel should not belong to the device.

I suspect that it doesn't. A kernel can easily belong to an application class (the XNA Game class holds a GameComponent collection; they are quite similar to your kernel and task classes), but I don't see how it could belong to a device. Furthermore, if the kernel belongs to the device class, what about the logger, the memory thing and the settings collection? If all them belongs to the device, then you have a problem, as any change is any of these is going to have an impact on the device class, meaning that you just broke the SRP.

Quote:Now that I have the engine interface decided, only one thing remains bothersome about my design. Currently, if the application would like to have some custom task done every frame, there must be a class that extends ITask and implements the update method with logic to preform said task. Then the user must add the task to the Kernel (this could be switched to use a factory later but w/e for now). I have been toying with the idea of switching ITask to a struct, lets call it sProcess, that would hold a pointer to a function. The process would be added to the kernel. The function would then be called each time the kernel decides to process the task. This would require the use of either functors or a bunch of static method calls (eww). At the moment I have nothing pushing me one way or the other. Can anyone see any problems with either choice?

The first one is definitely better: let the user extends the ITask class, implement his own task, and register/unregister it explicitly. This is how GameComponent(s) are implemented in XNA - it's easier to manage on your side, it's OO, it's simple and beautiful - with no nastiness inside -, and it's quite easy to setup on the programmer side.

On a side note, I suspect you don't have a clear idea of what your goal is - how the engine shall be used, what is the design philosophy and so on. Without a clear idea, it's very likely that you'll end up with something like Ogre, which is brilliant in some areas, and really horrible in some other areas. Anyway, I wish you good luck, and if you have any question, you already know where to ask [smile]

Best regards,

This topic is closed to new replies.

Advertisement