Engine Design Questions

Started by
17 comments, last by Shaarigan 4 years, 7 months ago

Not really a problem, it's more about having a deep understanding of what you're doing.  Decisions need to be based on that knowledge, and those fundamental assumptions are not part of the posts.

Smart pointers transmit not just the address of an object, but also the semantics of who owns the object.  They are part of the design of the lifetime of objects.

For most tools and libraries, the library itself doesn't actually own the objects nor do they do anything with their lifetimes. Objects are created outside of the system, they are passed to the system for use, then at a later point objects are destroyed outside the system.  In this scenario the tool or library interface should not use smart pointers.   That doesn't mean the user's code shouldn't use it, only that it doesn't belong in the library's API.

For some tools and libraries, they DO own the objects, serving both as the source that generates them and the sink that destroys them.  In this scenario it also usually isn't appropriate because the ownership of the object cannot change, the source is also the sink. The only potential problem is holding a reference after it has been destroyed, which usually indicates a software defect rather than the need for a ref-counted smart pointer in the design.

 

Typically it is a bad idea to mix memory across APIs or libraries.  As a simple example, one might be using the OS's standard heap, another might be using a debug heap, and still another may be expecting customized heaps for memory pools.  Libraries might accept an allocator object when they're constructed so memory can rely on the proper pool, but even that tends to be troublesome when they come from outside sources because every organization tends to have and use slightly different functionality.

As a result, when it comes to libraries the most universally consistent method is to only expose raw pointers or handles.  Either they accept raw pointers which the code guarantees have a suitable lifetime, or the library itself manages the memory and exposes either pointers or handles to the outside world. Neither of those scenarios is a smart pointer.  The ownership semantics that are the primary benefit of smart pointers don't exist in the scenario, in no case do the objects shift ownership.  

Advertisement
3 hours ago, SephireX said:

The engine api would only expose physics components and nothing about the physics engine or a wrapper. The components are themselves a wrapper for the user. The physics components would be written to suit the game programmer. My question is should the engine code talk to the physics engine or a physics engine wrapper and why?

My response does not fundamentally change. I would not consider a wrapper, but instead a boundary layer that is informed from the engine side of the code. The boundary layer can be very thin, like a set of helper functions to interface your engine components and types with the physics library types and APIs. As for why, so you have a divide between the code that is aware of the physics library, and the code that is not. If you have no boundary layer, then really you have an absence of architecture. Allowing library-specific code to proliferate through your engine's code will make it hard to swap out in the future. I do agree with @Randy Gaul that choosing wisely and sticking with it is the better solution overall.

2 hours ago, y2kiah said:

Allowing library-specific code to proliferate through your engine's code will make it hard to swap out in the future.

I disagree. I think a poorly written wrapper layer will make it more difficult to swap out in the future than simply using the underlying library directly. Creating a wrapper is a risk as it can easily have a negative effect compared to direct library access.

There is an incorrect assumption that a poorly written wrapper will do no harm, so by writing one at least there's a chance it will help. The reality is poorly written wrappers can do significant harm.

If you have no boundary layer, then really you have an absence of architecture.

This is also wrong. Choosing not to have a boundary layer can be the correct choice, meaning no boundary layer can be an appropriate architecture.

22 minutes ago, Randy Gaul said:

There is an incorrect assumption that a poorly written wrapper will do no harm, so by writing one at least there's a chance it will help. The reality is poorly written wrappers can do significant harm.

If you had read my response carefully you would know that I am not in support of a wrapper. You are incorrect in equating a boundary layer with a wrapper, they are not the same. You are also arguing for no "wrapper" based on the assumption that the wrapper will be poorly written. You have no idea if the wrapper will be poorly written beforehand and therefor cannot make a judgement about its level of harm or good to the code base. I also disagree with your statement that having no boundary layer is architecture. It may be the right decision, but it is still the absence of some element of architecture.

Hello All,

I just joined this forum. I found this post and it appears that I have similar questions with regards to smart pointers and wrappers. As a side activity in my free time I started working on my own engine. At the moment I am still working on the architecture so it is mainly UML diagrams. I wanted to make something similar to OGRE, with the support for multiple render libraries (Vulkan, OpenGL, etc). That is why I decided to go with wrappers for these libraries, using classes, polymorphism and so on.

A couple of weeks ago I watched a video from CppCon 2018, where a guy was explaining that using the OOP (Object-Oriented Programming) approach for game dev is a bad idea and we should switch to the DOD (Data-Oriented Design) approach if we want better performance:
CppCon 2018: Stoyan Nikolov “OOP Is Dead, Long Live Data-oriented Design”

Probably some of you will agree while others will strongly disagree with the guy from the video. My on the other hand went to do some research on the topic of OOP vs DOD and at the moment I am still wondering which way should I go with. Should I keep with the OOP approach or change completely to DOD or as I am more convinced to mix both approaches. So any suggestion on this will be helpful.

So to summarize. In addition to the 3 questions the OP has asked I have two more:
1. Should I go with OOP only, with DOD only or should I mix them both?
2. If I keep on with the OOP approach, should I consider using PIMPL (Pointer to Implementation) approach in the classes or is this a bad idea in the case of engine development since it will introduce one extra level of indirection and usage of smart pointers (most of the PIMPL examples are done using unique_ptr)?

Best Regards,
Ahmed  

We will make electricity so cheap that only the rich will burn candles

Hi there,

I think you should have started a new thread, but I will give you my thoughts on your questions.

 

2 hours ago, Xequtor said:

Should I go with OOP only, with DOD only or should I mix them both?

When I started with game-engine programming I had some similar issues. There are different approaches to a certain problem and people are quite dogmatic about which one is the "right one". Don't listen to those people. They live in a small bubble that just knows black and white. That doesn't mean that you should ignore them, just avoid following their conclusion about "the right way" for all and everything.

The best thing you can do is knowing WHY you use a certain approach. There is no reason to use one way exclusively. There is no right way. There is only something like the best way for a specific, well-defined problem that should achieve a specific goal (performance, clear interfaces, portability, etc.). So choose the tool that fits your specific needs. If you have a problem with performance due to a lot of cache misses, then have a look at DOD approaches that might solve it. If polymorphism makes your life easier without significantly slowing down your program, than use OOP.

 

2 hours ago, Xequtor said:

2. If I keep on with the OOP approach, should I consider using PIMPL (Pointer to Implementation) approach in the classes or is this a bad idea in the case of engine development since it will introduce one extra level of indirection and usage of smart pointers (most of the PIMPL examples are done using unique_ptr)? 

I had to look it up since I didn't know what it meant. If I got it right it is just an approach to hide your code and to reduce compile time. Do you need that? If yes, use it, if not, save yourself the pain.

 

Like I said before. Know WHY YOU are using certain methods and approaches. If you don't know which one to choose, you either need to learn more about your options or your specific problem. Afterwards, if you still can't figure it out, you are welcome to ask our opinion about a solution to this problem. ;)

Greetings

Hi 

18 hours ago, DerTroll said:

Hi there,

I think you should have started a new thread, but I will give you my thoughts on your questions.

You are right, I should have started a new thread, but since they are also related to the game engine design, I thought that maybe I can join an existing one. Thank you for your opinion and suggestion. Currently I am looking forward to hear any suggestion on this topic.

About the PIMPL. Hiding the code and reducing the compile time are not my main concern. Rather, my concern is this:

Quote

Making the binary interface (ABI) independent of the private members. It is possible to update a shared library without recompiling the dependent code, but only as long as the binary interface remains the same. Now almost any change in header, except for adding a non-member function and adding a non-virtual member function, changes the ABI. The PImpl idiom moves definition of the private members into the source and thus decouples the ABI from their definition.

So this is what I am trying to figure out. Is it worth adding that extra logic from the PIMPL idiom only to solve this concern or is there probably another solution. 

This is concern for me and it probably should be for anybody else, who is planning to share his/her engine with other users. And I think it should be considered with designing the engine.

Best Regards

 

We will make electricity so cheap that only the rich will burn candles

Most AAA Studios with their in-house engines tend to compile them into a static library these days because shared ones may cause some trouble and may reduce performance, especially if you have millions of calls per second, a single instruction more can decrease runtime performance and shared libraries may reduce code optimizations the compiler/linker (in the case of C languages) may provide.

Such examples are general engines that you make a 2D RPG and a 3D Shooter with at the same time, the size of the library remains the same regardless if your game uses the one or the other half of the code. Just my two cents!

The discussion of OOP versus non-OOP is ridiculous especially if there are articles (even here on GD.net) that proof themselfes right by using OOP related structures (std::vector<T> for example) to illustrate how you should avoid OOP ?

So don't spend two many thoughts on that, I'm mixing OOP and non-OOP code very often in my work because there are functions you don't need to be in a class while others have to. Especially in C++ you have multiple options to solve a problem, by using static functions with or without templates, template classes or just OOP structures

This topic is closed to new replies.

Advertisement