Irrlicht Design Problem (content coupling and encapsulation)

Discuss about anything related to the Irrlicht Engine, or read announcements about any significant features or usage changes.
nipou
Posts: 6
Joined: Thu Aug 26, 2010 2:21 pm

Irrlicht Design Problem (content coupling and encapsulation)

Post by nipou »

I would like to raise some important design issues in the Irrlicht engine. First, you must understand that we use Irrlicht for engineering, it involves the creation of tens of millions of triangles, all selectable. Unfortunately, poor implementation of the IrrArray class does not properly manage memory ; it is based on overly optimistic assumption of no heap fragmentation and it is always possible to allocate a contiguous area of memory as large as desired, this is false. The IrrArray class should manage the failure of malloc and work with pieces of memory it would represent a continuous memory array to the class users.

We had decided to modify this class when we saw that the principle of data encapsulation was not observed. Indeed, the method IrrArray::pointer() can directly access the internal data structure and it's used everywhere in Irrlicht causing a content coupling that is considered by most authors as the worst (the stronger) coupling (see The Software Engineering, Pressman). Thus, it would change the entire framework to provide a viable solution.

We are waiting for an idea to solve the problem.
Thank you in advance.
Nicolas Poupart, M.Sc.
Architecte Logiciel / Software Architect

SGS Canada Inc.
10 boul. de la Seigneurie Est Suite 203
Blainville, Québec, Canada J7C 3V5
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

The idea of an array is just that - one large block of memory. Basically the same as the stl::vector. And yes - it expects that the OS does indeed deliver the memory (I think you get an std::bad_alloc exception otherwise). For your needs using a list might be the better solution. That structure does allocate memory for each element and not one large block.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

If you really need a directly accessible list of elements using non-contiguous memory, you have to develop it on your own. Or take some existing solution from the net, there should be many existing solutions. The thing is that many places inside the engine require this "one memory chunk" assertion. Very often, especially when talking to the underlying graphics APIs, the data is passed via pointers and memory areas. There's no way to overcome this situation. So most things related to directly rendered data (which includes vertex and index lists) must be in a contiguous memory area. This is also why we do need the pointer() method, although in most places the const_pointer() method would be enough.
I don't know exactly the setting of your application, but you should try to detangle the view and model/controller parts of your system. Make sure you split your objects in logical groups (which map to meshbuffers) and place your identification and selection scheme on top of this structure.
nipou
Posts: 6
Joined: Thu Aug 26, 2010 2:21 pm

Post by nipou »

CuteAlien wrote:The idea of an array is just that - one large block of memory. Basically the same as the stl::vector. And yes - it expects that the OS does indeed deliver the memory (I think you get an std::bad_alloc exception otherwise). For your needs using a list might be the better solution. That structure does allocate memory for each element and not one large block.
I want but I can not.
hybrid wrote:If you really need a directly accessible list of elements using non-contiguous memory, you have to develop it on your own. Or take some existing solution from the net, there should be many existing solutions. The thing is that many places inside the engine require this "one memory chunk" assertion. Very often, especially when talking to the underlying graphics APIs, the data is passed via pointers and memory areas. There's no way to overcome this situation. So most things related to directly rendered data (which includes vertex and index lists) must be in a contiguous memory area. This is also why we do need the pointer() method, although in most places the const_pointer() method would be enough.
I don't know exactly the setting of your application, but you should try to detangle the view and model/controller parts of your system. Make sure you split your objects in logical groups (which map to meshbuffers) and place your identification and selection scheme on top of this structure.
The problem is the bad desing of Irrlicht : content coupling and no-encapsulation of data. We can always patch for poor design by acrobatics : I will create a super mesh which encapsulates several meshs.

A good design would require an API for IrrArray into two distinct groups, the high level interface with encapsulation for high-level classes such as Mesh-selectors and a low level interface for accessing internal structures (assuming it's not a single block of data) for coupling with the lower classes as the drivers. To limit the use of content coupling, access should be protected by mechanisms such as the friend keyword.
Last edited by nipou on Thu Aug 26, 2010 7:16 pm, edited 1 time in total.
Nicolas Poupart, M.Sc.
Architecte Logiciel / Software Architect

SGS Canada Inc.
10 boul. de la Seigneurie Est Suite 203
Blainville, Québec, Canada J7C 3V5
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

Such arrays are the fastest (and probably only sane) way to send data to OpenGL/DirectX (the hardware API's). Which is in a realtime 3D engine a more important design consideration than trying to design for a special case which can be solved otherwise anyway.

And it's not really nice to come new in a software forum just to tell the software is bad and poor design because you have a problem with some corner case and read some general rule in some book about how de-coupling is good. I got another rule for you: Good software design is about making the common case easy and the special case possible.

If you have unusual geometry then it will just be a little more work for you. Not that splitting up a mesh is really that much work...

If you have more details on what you are doing or trying to do and in which problems you run people might have more ideas and will try to help you. But I would appreciate it if you could stop now flaming the design unless you actually know what you are speaking about or have concrete ideas for improvement.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
nipou
Posts: 6
Joined: Thu Aug 26, 2010 2:21 pm

Post by nipou »

CuteAlien wrote:Such arrays are the fastest (and probably only sane) way to send data to OpenGL/DirectX (the hardware API's). Which is in a realtime 3D engine a more important design consideration than trying to design for a special case which can be solved otherwise anyway.

And it's not really nice to come new in a software forum just to tell the software is bad and poor design because you have a problem with some corner case and read some general rule in some book about how de-coupling is good. I got another rule for you: Good software design is about making the common case easy and the special case possible.

If you have unusual geometry then it will just be a little more work for you. Not that splitting up a mesh is really that much work...

If you have more details on what you are doing or trying to do and in which problems you run people might have more ideas and will try to help you. But I would appreciate it if you could stop now flaming the design unless you actually know what you are speaking about or have concrete ideas for improvement.

«trying to design for a special case which can be solved otherwise anyway».
The use of a large amount of triangles is not really a special case. Just a greater step in Irrlicht technology.

«Which is in a realtime 3D engine a more important design consideration than trying to design for a special case which can be solved otherwise anyway
A patch is never a good solution. If managing a large number of triangles is a functional requirement for Irrlicht, take this up at the framework fundation.

«If you have unusual geometry then it will just be a little more work for you. Not that splitting up a mesh is really that much work...
Why do not improve Irrlicht ? It's perfect you think.

«If you have more details on what you are doing or trying to do and in which problems you run people might have more ideas and will try to help you. But I would appreciate it if you could stop now flaming the design unless you actually know what you are speaking about or have concrete ideas for improvement....

The hysterical reactions of wounded vanity go nowhere.
Nicolas Poupart, M.Sc.
Architecte Logiciel / Software Architect

SGS Canada Inc.
10 boul. de la Seigneurie Est Suite 203
Blainville, Québec, Canada J7C 3V5
Strong99
Admin
Posts: 687
Joined: Fri Mar 31, 2006 7:06 pm
Location: Netherlands
Contact:

Post by Strong99 »

Irrlicht is opensource, so you could start implenting it yourself and make use excited about it?

Where you could start is by adding your API while trying to keep it backwards compatible until you finish implementing your practice. This Could allow you to slowy change the problems you encounter. If done right it will give you two seperate APIs which would work together as good as possible until you're finished. If you got a good working demo you could always demonstrate it to us, taking speed, memory and usability in mind.

The previous reactions are rather childish and don't really encourage us to reply. Every Best-practice has its pro's and cons. The same for OOP. For example the friendly keyword is also seen as evil and confusing and thats why other OOP languages didn't implement it. See c# for example. So my point is, there is no real boundry between good and bad. It depends on the case.
nipou wrote:access should be protected by mechanisms such as the friend keyword.
CuteAlien wrote:Good software design is about making the common case easy and the special case possible.
[edit] I see you changed your post before I finished posting
Sylence
Posts: 725
Joined: Sat Mar 03, 2007 9:01 pm
Location: Germany
Contact:

Post by Sylence »

Actually there is nothing we can do about this. When OpenGL/DirectX wants one big block of memory Irrlicht has to give them one big block.

Of course it would be possible to use lists instead of arrays in the in interfaces. Still we would need to copy all the elements of the list into an array to give the data to the underlying graphics API.

Correct me if I'm wrong but there is no advantage over the current way.

Oh and btw: Why do you use Irrlicht if its so "bad" designed?
Software documentation is like sex. If it's good you want more. If it's bad it's better than nothing.
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

nipou wrote: The problem is the bad desing of Irrlicht : content coupling and no-encapsulation of data. We can always patch for poor design by acrobatics : I will create a super mesh which encapsulates several meshs.

A good design would require an API for IrrArray into two distinct groups, the high level interface with encapsulation for high-level classes such as Mesh-selectors and a low level interface for accessing internal structures (assuming it's not a single block of data) for coupling with the lower classes as the drivers. To limit the use of content coupling, access should be protected by mechanisms such as the friend keyword.
There's absolutely no problem to create a mesh with a few million triangles. In most cases, this will render slow as hell, but it works. Even with the current solution. If your memory is fragmented and cannot provide the necessary memory allocations then it's usually not our problem. We develop Irrlicht also for mobile systms with very limited memory. Still, we can get all the renderable meshes on those systems with the current setup. There's no need to do anything special.

However, as also said by others here, there's no way to solve this problem efficiently. After all, it wouldn't be possible at all. The underlying gfx apis require the whole data in one consecutive memory area. Even if we would abstract away this limitation on the higher API levels, we would be forced to create the large, consecutive memory area each frame. For the situation given, this is not possible. Hence, we couldn't render the model even if our high-level containers would support it. Sorry to say, but this is a no go.

There are ways to overcome these problems with recent additions to OpenGL and DirectX, but those are only available in newer versions of the APIs. So it wouldn't be a general solution. We may add them to Irrlicht for use in certain situations, but this is a completely different story.

However, just sketch a possible solution. You're the architect, so I assume you can draw a diagram which contains the full solution with all side conditions and requirements. Give it a go and convince us. If you succeed, we can implement this without problems.
slavik262
Posts: 753
Joined: Sun Nov 22, 2009 9:25 pm
Location: Wisconsin, USA

Post by slavik262 »

nipou wrote: «If you have more details on what you are doing or trying to do and in which problems you run people might have more ideas and will try to help you. But I would appreciate it if you could stop now flaming the design unless you actually know what you are speaking about or have concrete ideas for improvement....

The hysterical reactions of wounded vanity go nowhere.
As do your accusations. To spare you the joy of being able to criticize our "hysteria," and instead force you to focus on the discussion at hand, I will try to respond as concisely and fully as possible.
nipou wrote: «trying to design for a special case which can be solved otherwise anyway».
The use of a large amount of triangles is not really a special case. Just a greater step in Irrlicht technology.
Irrlicht, as with other DirectX and OpenGL themselves, must be relatively low-level. Real-time systems, being real-time, always give priority to speed of execution. There is a reason that many embedded systems still use C or even Assembly; high-level software architecture means nothing if the end-result does not meed the demands set upon it. Do not read this as me excusing bad code. It is simply a fact that many times in real-time software, certain parts of high-level software architecture are a luxury that cannot be afforded.

DirectX, OpenGL, and Irrlicht (which is built on them) reflect this in their design. Abstracting the coupling of data from the classes which handle them more than is already done would compromise the emphasis on real-time and fast access. I challenge you to find a real-time graphics API that doesn't permit raw access to data for speed of execution. Seeing as DirectX and OpenGL demand this without exception, it is fundamentally impossible.

C++ itself was designed with the same ideas in mind - allowing high-level architecture while maintaining fast access close to the hardware. If you have a problem with easy and direct access to the underlying data, you'll have to take it up with not just the Irrlicht community, but Bjarne Stroustrup.
nipou wrote: «Which is in a realtime 3D engine a more important design consideration than trying to design for a special case which can be solved otherwise anyway
A patch is never a good solution. If managing a large number of triangles is a functional requirement for Irrlicht, take this up at the framework fundation.
This is not a patch, and managing the extreme amount of triangles you suggest is not a fundamental requirement of Irrlicht, but only your software. As you can see from the hard work of many around this forum, Irrlicht works perfectly well for its intended purpose: a real-time graphics engine with emphasis on speed.
nipou wrote: «If you have unusual geometry then it will just be a little more work for you. Not that splitting up a mesh is really that much work...
Why do not improve Irrlicht ? It's perfect you think.
Nobody said that Irrlicht is perfect, nor that it doesn't need to be improved. You come here spouting software engineering buzzwords and criticizing the engine without providing a single solution of any type. If you want to have a serious discussion, stop trolling and start holding an intellectual conversation about what can be done to improve the engine.
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

Just try to describe your problem in more detail. Why do you need to have a single mesh with several million polygons? And if you have such needs - why does it have to run on systems without enough memory for that (memory is cheap these days...)? And why are you getting such bad memory fragmentation (that happens in my experience mostly on some embedded OS systems)?

We can try to find a solution. Changing the array class is not likely a good idea, but maybe there are other ways to help your project.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
nipou
Posts: 6
Joined: Thu Aug 26, 2010 2:21 pm

Post by nipou »

The problem it's not the memory allocation of an IrrArray at mesh level since it's divided into mesh buffers of fixed size, requiring a continuous memory allocation of 65536 (triangles) * 9 (float / triangles) * (4 bytes) = 2.36 MB.

It's the composition IMesh <>---- IMeshBuffer that allows the continuous memory allocation to be always good (below 10MB, it's good). But the allocation failed for a triangle selector when creating it's octree because it's performed on all triangles of a mesh (dynamic allocation gonna crash soon, one million voxels require 12 million triangles ; 432 MB of continuous allocation).

To overcome the problem, there are several top-down solutions as simply divide the IMesh.

But the real problem lies in the fact that all current and high-level features, using an IrrArray will be limited to operate only for a small mesh. More troubling, direct access to internal structures of IrrArray seems to become an habit even when it's no longer needed for coupling with low level modules.
Nicolas Poupart, M.Sc.
Architecte Logiciel / Software Architect

SGS Canada Inc.
10 boul. de la Seigneurie Est Suite 203
Blainville, Québec, Canada J7C 3V5
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

To be honest, this is just the result of certain laziness in the implementation of the octree structures and related issues. It's probably quite wrong to use just one array for this thing, as it would be much more efficient to organize this with some other conditions and requirements to be met. But again, this is not a problem of the array container per se. You can implement working solutions with the existing containers and structures without noticeable overhead. Moreover, efficient implementations would probably even require to adapt to the restrictions of typical array sizes.
Anyway, if you'd have described your initial problem better in the first place, we could have agreed on this far earlier. Please raise a request ticket on our SF trackers so we remember this.

Edit: It's even far lower than I expected. The problem is the implementation and also the API of the CTriangleSelector. The API gives only a pointer to a large triangle array (raw array), which means that you need one large area again. Also, the current implementation of the selector also uses consecutive memory for the whole mesh, that's right. However, since the API requires this, it won't really help to change the underlying stuff. Still, both things might be extended, but this requires much more work than I thought. I.e., nothing for the next release.
nipou
Posts: 6
Joined: Thu Aug 26, 2010 2:21 pm

Post by nipou »

hybrid wrote:To be honest, this is just the result of certain laziness in the implementation of the octree structures and related issues. It's probably quite wrong to use just one array for this thing, as it would be much more efficient to organize this with some other conditions and requirements to be met. But again, this is not a problem of the array container per se. You can implement working solutions with the existing containers and structures without noticeable overhead. Moreover, efficient implementations would probably even require to adapt to the restrictions of typical array sizes.
Anyway, if you'd have described your initial problem better in the first place, we could have agreed on this far earlier. Please raise a request ticket on our SF trackers so we remember this.

Edit: It's even far lower than I expected. The problem is the implementation and also the API of the CTriangleSelector. The API gives only a pointer to a large triangle array (raw array), which means that you need one large area again. Also, the current implementation of the selector also uses consecutive memory for the whole mesh, that's right. However, since the API requires this, it won't really help to change the underlying stuff. Still, both things might be extended, but this requires much more work than I thought. I.e., nothing for the next release.
Thank you, but I am still sincerely convinced that resolving the problem at the base by providing a data structure managing perfectly the dynamic allocation in a transparent manner would be much more practical than trying to solve this problem ad hoc, each time. This type of low-level management should be the responsibility of a dedicated class (IrrBigArray?).

But, it's up to you
Nicolas Poupart, M.Sc.
Architecte Logiciel / Software Architect

SGS Canada Inc.
10 boul. de la Seigneurie Est Suite 203
Blainville, Québec, Canada J7C 3V5
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

the problem here is once again, that many of those structures will be fed into the gfx APIs. So at some point we need to make a decision whether the data might meet the gfx API, or never in its whole life time. For all the latter, another container could be used. But due to reuse and simplification, an optimized algorithm should bring far better results in the end. So yes, we keep the original arrays and will work around those places where they are overused.
Post Reply