The problem with approaches requiring extra discipline is: it's an extra mental burden to bear while programming. Also, you'll always be limited by the fact that you're working in a less pure ecosystem and will likely end up using libraries written in "not very sane C++" anyway.
We seem to have flexible compiler SDKs these days (LLVM etc.), why isn't there a strict "sane C++" or "orthodox C++" subset available as a custom language or compiler option yet?
Probably because sanity is (1) subjective, (2) in this case doesn't really come from disabling features (restricting language). He is still using most of the C++ features (overloads, operator overloading, templates) but only in semantic context that is obvious for them (i.e. templates for collections, overloaded operators for vectors and matrices). Therefore it might be a little bit hard to create a compiler front end that would understand which class acts as a collection or whether given structure is implementation of well known mathematical concept.
In particular since C++ kept the C-style preprocessor "just copy and paste that file in there" includes it would be tricky to handle "sane C++" including "full C++", especially since templated C++ tends to put a massive amount of code in the headers (think boost for instance, which is mostly .hpp "headers"). You'd have to tell the compiler to switch the "sane" flag on and off within the same translation unit depending on the original source of the code. Nothing impossible, but not exactly elegant. Alternatively you could use a new `extern "sane-C++" { ... }`-type block around all your code to tell the compiler what to do.
At any rate you'll have to make sure that your sane subset can always inter-operate with the "wild" C++ without any cross-contamination.
But really I think the main problem is that you'd have trouble getting a consensus on what would be your sane subset. Some devs will tell you to get rid of exceptions altogether, others will tell you that multiple inheritance is the work of the devil. Some will want to ban raw pointers (or at least severely gimp them). And some will want none of that but something else instead.
This can probably be solved by adding flexible configuration options (C++ compilers already have plenty). In practice, project leaders would then decide on a set of such options, similar to what happens with code style guidelines.
Perhaps some day a majority of people will agree on a "good" subset of C++ / option set and it will become standardized as a new language or dialect.
https://www.youtube.com/playlist?list=PLmV5I2fxaiCKfxMBrNsU1...
If you use the flag -Werror all warnings will be errors.
Then add -Wall which enables all unambiguously good warnings, which will stop a whole lot of things that skirt the type system, it will prevent silly rounding bugs and in general reduce the bug surface of your code.
Then if you enable "-pedantic" more errors are found, but not all are clearly improvements. I think most of them are good and I think most would agree that mandating the "override" keyword is good, but not all would agree with every signed to unsigned comparison warning.
Put together this add "-Werror -Wall -pedantic" or a "/w4" MSVC) to the command line, or more likely makefile/CMakeLists.
I personally advocate enabling all these and a similar set from msvc then adding a set of robust warning suppression macros to you code and suppress the warning that really make no sense to fix. This has prevented a ton of bugs in my code, it minimizes the amount of premature optimizations I see in code that tried to cast from one type to another by relying on some unspecified binary compatibility and in general makes writing C++ really enjoyable.
For example, this is what Oryol achieves, targeting OpenGL on native and WebGL on web. I don't think anything could beat the "Orthodox C++" approach for that purpose, aside from using C (which is what I'm using for a project right now).
It is, however, a rather restrictive standard with critical systems in mind. Usually, you don't want such restrictions in regular software development, you want the full power of the language.
Ensuring sanity is better served with static analyzers (linters) that can be tailored to the specific needs of the project and be regularly updated with new rules.
But I would also love to have it as a compiler switch.
But also, i don,t see any sane reason to reinvent the wheel and reimplement basic stuff like thread/mutex classes when the C++ version works well. Or using "NULL" instead "nullptr", or using that pre-processor macro garbage instead templates/constexpr, etc
I think some modern C++ features, that if well used, turn the code much clear and expressive.
I'm fine with nullptr, it is going to replace NULL very soon.
- dozens to hundreds of compiler error lines for a single error, where it's hard to find out what the real problem is (IDEs often point to the wrong line)
- Code is hard to follow. E.g. try to figure out from boost asio source code which code is actually used if if you do a async_read(socket). I personally gave up after the second level of template substitutions, and have only a chance to follow the execution path in the debugger.
- Besides goto definition also other IDE features do not work really well with templates. E.g. no autocompletions for constructors with make_shared.
Not to be too grumpy but game engines are a weird thing cause they are such complex beasts and usually the people using them have invested a lot of time in learning them. It's fun to make a new engine, but it probably doesn't have the community nor interest to cover the hard 10 to 20% that inevitably come up. Seems like the time is better invested in one of the super big AAA engines or in writing your own.
What's the plan for 2 to 3 years from now?
https://news.ycombinator.com/item?id=5442366
Polycode had some interest around here 4 years ago, with lots of the same goals, but haven't really heard from it since. Similarly, how does this compare to Godot or even something like Torque?
"Don't use anything from STL that allocates memory, unless you don't care about memory management."
I now mostly avoid templatization in my own code unless there's a really good reason. But the standard library often lets me avoid explicit memory allocation. Would love to hear more about the motivation for this (and other aspects of your C++ usage).
Also, if you have a demo of the engine in use that would be fun to see!
I don’t think it’s widely used outside of the Gnome desktop world, but there’s quite a few apps written in it out there.
https://github.com/bkaradzic/GENie
Appreciating that CMake has its warts, it also has a ton of mindshare and has lots of convenient modules for handling common dependencies. What are the motivations to use a Lua-based scheme instead?
Like, the fact that it uses an old version of Mono on not-Windows, which uses a mark and sweep garbage collector. You end up with frequent stop-the-world garbage collection pauses that freeze the screen for seconds at a time. Play any Unity game on the PS4 and you'll see it frequently.
I've also heard that Unity's project asset management doesn't really work for teams that have more than 10 people working together, but that's something I don't have direct knowledge of.
This is a coding problem, not an engine problem. A game should have almost no dynamic resource allocation.
On mobile, and/or desktop there are very few empirical reasons not to use Unity. If you have the skills to develop your own engine and tools, then you certainly have the skills to work around Unity or garbage-collection issues.
When I'm hiring a game developer and they'd rather work on engine or tools than making games, this is a red flag. I've seen projects waste person-decades of development effort all because one or two senior devs wanted to do roll their own thing instead of using Unity.
The engine and toolchain that is always has flaws that the utopian engine and toolchain that could be don't have (yet).
If you're expecting people to see this as a useful product, please explain why it should be used instead of the existing game engines. Consider listing its features and comparing them, maybe in a table, to popular game engines. Deferred rendering? Multi-threaded rendering? Entity-component system? etc.
Right now, I can see it has a (nice looking) editor, "physics" (nothing's moving, so it's hard to tell), and "animation" (again, nothing's animating in the image). Alas, one needs to learn to use its entirely fresh standard library reimplementation, and likely its strict C++ subset, if one actually wants to be productive with it. Given apparently no active community, no sample (or actual) games written in it, and no paid support, I don't see how that would be feasible.
If you have no interest in actually making a competitive, novel, or useful (to others, compared to existing solutions) game engine, please just say that. In that case, I'd just say this is a neat side project: well done, but try to focus on building something with it to help sell/prove its features.
Nobody's trying to sell anything here.
That is not to say it is always needed though if the game is really simplistic. But there are a lot of engines capable of rendering instanced bouncing OBJs out there.
In the last (commercial) engine i worked at, the pipeline was to export the mesh from the 3D mesh editor (3dsmax or maya) to a custom easy to parse format and then import it from the editor to a more compact faster and easier to work with format. Then the artists would create the materials and other resources that the engine needed to work with from inside the engine's own tools. The imported resource remembered the original file so that artists could simply export again and ask the editor to reimport stuff (at a later point we made the editor to automatically monitor the directories for changes - both Windows and Linux provide functionality for this - so the artists would simply export from their 3D mesh editor and the engine's editor would reimport the meshes automatically).
In the previous (commercial) engine i worked at, things were simpler in that we only supported 3ds max (although the 3dsmax SDK was far from simple, if it wasn't for SymbianOS it would be one of the worst SDKs i've worked with... but that is another story) and we exported animations and meshes directly to a custom format the engine expected. The exporter also had a "preview" feature to allow the artists preview the exported files in a standalone viewer that used the engine's renderer to make sure that things looked fine (in that case we actually did try to use 3ds max's materials, although in hindsight that was a mistake since even with the viewer the artists often assigned textures incorrectly - we should have relied only as little as necessary on 3ds max instead of making it the primary content editor).
This speaks about language design and communities, approach to development practices.
Related, Andrei Alexandrescu had a great talk about allocators in C++ a couple of years ago: https://www.youtube.com/watch?v=LIb3L4vKZ7U
Also related to the Orthodox C++, the same engine also ousted exceptions and RTTI. I'm not sure about the reason for exceptions, but C++'s RTTI was simply inadequate and instead it was replaced with a custom one made using macros (similarly to wxWidgets and MFC) that allowed automatic object serialization and reflection which was used for all saving and loading, exposing objects to the editor automatically with a common UI and exposing classes and objects to the (custom) scripting language with very little setup.
Interestingly most of the stuff the engine had to reinvent seem to be first class citizens in the D language. Also Andrei's allocators also seem to be available (experimentally) there too.
Personally i prefer plain old C (C89 even, although with a few commonly available or easily reproducible extras like stdint) because i see C++ as too complex for what it is worth. However D seems to provide more power with less complexity and more and more makes me want to try it, especially the new "better C" mode that DMD has got (which i think is somewhat the D equivalent to Orthodox C++ that is linked from the page).
Strings have arbitrary sizes. How does pooling them together reduce fragmentation? Do they always come and go in groups?
This seems a little like cargo cultism. I wonder if any of these shops regularly measure the performance of their custom containers and compare with the standard library on a modern optimizing compiler and make a reasoned judgment that it's still currently worth the trade-offs to stick with their own stuff.
You can find demos in the `samples` folder. :)
I know EA wrote their own implementation of STL[1] to get around the problems that the standard implementation was causing in their game engines. Doing a 'diff' between that and a standard implementation should highlight some of the potential problems they found.
Anyways, C++ is much easier to manage than any GC language for a real time application such as this. And your Rust "pun" is a rhyme.
http://fabiensanglard.net/doom3/index.php http://fabiensanglard.net/doom3/interviews.php#qc++
D or Nim (or even C!) would make more sense.
Surely there are better reasons to disregard Go for gamedev by now.
Better let the Unreal guys know about it then.
Rust, however is great.
Yet Go uses the oh-so-error-prone return value error checking that has been so successful in C :/
With Go you get a compiler error if you don't do something with that error. You have to explicitly decide to ignore it with `_`. As far as I remember that's quite different from C where you can get an error code, ignore it, and never realize you've missed it.
try:
...
except: passSee also: https://geometrian.com/programming/tutorials/write-games-not...
I am interested in developing AR applications that are not as much of a game, so I was worried that a game engine would not fit.
With both learning more about graphics and making my own games, it would make future engine development more clear.
Search for "Bitsquid" and "Our Machinery": pure gold IMHO.
I guess. The question is why should I have to work around Unity?
I mean, everything is a trade-off. I understand that for many teams and games, the hassles of Unity are worth the benefits. But that is not every game and every team.
>The engine and toolchain that is always has flaws that the utopian engine and toolchain that could be don't have (yet).
We don't have to compare Unity to utopian engines and tool chains when we can compare it to its competitors like Unreal.
When shipping a production-quality game there's almost always going to be something you have to customize or work-around with any engine. What often happens with home-grown engines is that the cost of tools friction or engine implementation is not properly accounted for, because it's kind of fun although it's unproductive.
> But that is not every game and every team.
Agree, but I'm fed up with the amount of FUD around Unity. I've watched teams burn money rather than putting up with some annoyances. I'm an older dev (40+), so I've seen many iterations of devs refusing to use existing tool X in favour of supposedly more convenient but less battle-tested tool Y. This is in game development, and software development, more generally.
There are also holistic benefits to Unity, like 1-2 second recompiles. This is a game-changer in terms of debugging and allowing you to try more iterations of things.
> We don't have to compare Unity to utopian engines and tool chains when we can compare it to its competitors like Unreal.
I think Unreal is a great engine, and I'm admittedly less familiar and therefore productive with it than with Unity.
That said, I would say that for mobile or small-footprint games Unity still has an edge. This is based on the experiences of several studios / devs that I've talked to. They make great headway with Unreal, but then the project bogs down when it's time to actually ship. That said, this could be Unreal FUD from developers who are new to that engine.
To get the benefits of Blueprints in Unity, just buy PlayMaker. It's $65 dollars a seat, and you'll never write another Finite State Machine.
If everybody makes the same coding problem, and the common denominator is one of their dependencies, it makes you wonder what's wrong with the dependency.
https://www.youtube.com/watch?v=mQ2KTRn4BMI
I don't know what projects you're referring to specifically on the PS4 but I'd suspect the difference comes down to hardware spec on the PC masking the issue, it probably being the primary platform and/or lack of time to work on optimisation for the port.
It's the use of C#, which generally written in a manner that creates garbage and lack of experience with programming games that causes many people who use it to go wild with allocations. Both intentionally and by accident. These days you can actually get pretty far before it's ever a problem on a gaming PC. Unity is also incredibly accessible so more people with less technical chops are programming games without even opening the profiler.
CMake is "too complicated" and "you need to be an expert". Understandable, I suppose. There are certainly specific things in CMake which are pretty terrible, like the add_custom_command/add_custom_target dance, but from the perspective of someone who has had to become an expert in it (via ROS/catkin), I would be unlikely to give it up. There's just way too much stuff it gives you for free, especially when it comes to things like packaging, testing, etc.
GENie seems to be focused first and foremost on a project/solution-oriented IDE workflow, with the Makefile generator as the one that's tacked on. So I can definitely appreciate that if you're working on a project where everyone's in an IDE anyway, it would make sense to use a generator that has the IDE's concepts as a first class citizen.
What do you do regarding collections? (Dynamic arrays, hashmaps)?
I don't do anything!
I use the most appropriate data-structure and minimal transformation necessary to get the work done. I use maths and higher-level tools to verify my designs but the implementation, when required to be soft-realtime needs to exploit as much mechanical sympathy from my target platform as possible.
You might be interested in https://dataorientedprogramming.wordpress.com
Cheers!
#define TYPE int
#include "list_template.h"
#undef TYPE
with `list_template.h` using TYPE wherever a data type would be needed and defining inline (C99) and/or static (C89) functions so that they can be redefined in multiple files (or have a dedicated C file that includes the above header with all data types and an additional macro that enables the implementation). This is basically sort of implementing templates in C.The void pointer approach is the simplest and most macro free (despite me using macros here, i'm a bit macro happy sometimes :-P) but at the same time you are limited to pointers. In practice i've found that most of the time this is enough, which is why i still haven't replaced that yet. But there are cases where i'd prefer to be able to have a list of structs instead of pointers to structs, both because it is simpler (no need to define a custom free function) and faster (less indirections), so i'll most likely replace that code with another approach (most likely the macro that defines the types, not the include header).
But if there is a single feature i'd like to see from C++ to C that would be templates, even if they are single depth. I don't even care about classes or the other stuff (classes are nice to have, but not necessary as long as the compiler can figure out that the template parameters to structs and functions with the same name refer to the same type when used together).
I have a custom loader that deals with it: https://github.com/dbartolini/crown/blob/master/src/lua/lua_...
https://www.lua.org/manual/5.1/manual.html#pdf-package.loade...
I keep wondering if it's not possible or if there's just not a lot of overlap between Go programmers and gamedevs? Wish someone knew.
Go users interested in diving in to SDL or OpenGL bindings to make a game shouldn't be discouraged. Lots of games are made in all sorts of high level languages, the heavy lifting is put onto a few native libraries. But if the goal is to make a general engine, I'd question its utility apart from fun/learning. Again there are game engines in high level languages (with dark native-level secrets in any that try to be performant) but they don't seem to get traction. Even an engine in e.g. C++ doesn't necessarily help your performance goals (http://www.yosoygames.com.ar/wp/2013/11/on-mike-actons-revie...) if your plan is to make it general instead of make it just support whatever sorts of games you're making and planning to make.
import "os"
func main() {
os.Open("this file does not exist")
}
This will compile just fine, producing no compiler error or warnings whatsoever. The error is just silently ignored.Compare to Rust:
use std::fs::File;
fn main() {
File::open("this file does not exist");
}
This will produce the following warning: warning: unused result which must be used
--> test.rs:14:5
|
14 | File::open("this file does not exist");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unused_must_use)] on by default
This example is a bit contrived, because if you open a file you probably want to do something with it. But imagine something where the only return value you care about is the error, like say "txn.commit()"Obviously this is not nearly as convenient as your Rust example, but enables some of its the benefits.
Also if one doesn't allocate like crazy on the heap, there is no reason the GC needs to work.
If you never run out of memory you'll also never need to GC. ;) Or even free(), just let the program finish and reset the machine. (Actually not too weird in some embedded systems...) Some languages make it easier to not heap allocate than others, or notice when you are heap allocating. I hear Go does better than Java in this regard. But if you're facing a performance issue at the level where you're fighting the GC as the biggest barrier, and the language doesn't give you much assistance (like being able to choose latency/throughput tradeoffs or controls on non-determinism), that's a sign the language isn't that suitable for that performance problem domain. With performance sensitive games, you're already in the corner of having to worry about hardware details, so there's a strong incentive to just start the fight at the beginning without your hands tied by some language's static GC.
True, but just because a programming has language level GC, it doesn't mean it must be used everywhere.
If one doesn't allocate like crazy on the heap, there is no reason the GC needs to work.
Also using value types is always an option.
One also doesn't call malloc() in such high-performance subsystems.
Part of it is also for cross-platform consistency. No chance for bugs caused by using a different stdlib, etc. This is worth it when a lot of the toolchains for consoles are arcane have the chicken/egg-ish problem of often having poor stl implementations since they expect everybody to implement their own.
I've been out of game dev for a while though. These days I'd expect using the C++ stdlib to be more common, and mallocs are faster now (although even if you're bundling e.g. jemalloc, I imagine you still get a substantial benefit from using pools or arenas in many cases).
As other comments have said though, the main reason it's used (and the reason I was interested even though I don't do games) is because the allocation story in the stdlib sucks.
(libstdc++'s vector looks sensible in this respect - a good decision on their part. Haven't looked at any other aspects of it though.)
At one point the contents of vectors were often inconvenient to examine in just about every debugger, because you'd have to type out some infeasibly long expression to get at them, "vec._Mybase._Myval._Myptr[0]", that kind of thing, which you could also fix by writing your own container and simply calling your pointer field something like "p". (Same goes for smart pointers.) Luckily this is much improved in the latest Visual Studio but it may still be an issue elsewhere.
Yes, while it wasn't often, we sometimes did benchmark tests to improve performance when bottlenecks were found, especially towards the game's release when we were focusing on optimizations. IIRC we did some minor changes in the dynamic array container and we rewrote the hashmap and hashset implementations. One of the programmers wrote a performance test comparing several algorithms both with synthetic and real data (from the case that created the bottleneck).
> compare with the standard library on a modern optimizing compiler and make a reasoned judgment that it's still currently worth the trade-offs to stick with their own stuff.
There are other reasons to use a custom container than just the pure performance of the container itself. One is using a different allocation scheme, as the example i gave in the grandparent post, another is to use a friendlier API (see `find` and friends) and add more features. An important one in our engine was support for the custom RTTI that was used for object serialization and the scripting language that also worked and exposed those directly - the container, the RTTI implementation and the scripting runtime had to have intimate knowledge about each other to work transparently (especially when the editor entered the picture, where you could create new entries, often objects but also sometimes structs or other data types, by editing the array directly in a property editor).
Of course not all engines do that and TBH most of the performance and memory related bits are more relevant to consoles than (desktop) PCs (the API friendliness and RTTI stuff are platform agnostic though :-P). At the previous gaming company i worked at, the engine used standard containers. Also AFAIK the engine used by the Two Worlds games also uses standard containers (based on some of their developers' comments).
Personally when i write C++ i implement my own containers not because of performance but simply because i dislike the STL API - for example i want to have "Find", "IndeOf", "Swap", etc methods in the container itself :-P. Sadly it seems that i'll also need to do the same if i decide to start working with D seriously since D's standard library seem to more or less copy the STL API style.
You don't know? Maybe you should find out before slinging around accusations of cargo cultism.
class Foo {
public:
static std::optional<Foo> create();
private:
Foo();
};This now means that you can't use any constructors, so how do you have Containers of foo?
If fact, the majority of them gets abandoned even before memory pressure starts to be a relevant issue.
Even if Go isn't at the same level of D or Modula-3 in regards to memory management (heap, stack, global), it is already quite usable for many types of games.
As I mentioned in another comment a lot of fun games have been made in all sorts of languages. That doesn't really make any of them suitable for games though, and you'll still find far fewer examples of game engines in GC languages.
Game developers have a tendency to only update their tools when OS or console vendors force them to do so.
As for "why not stick with C"--all of the other reasons still hold true, from templates on down. The simple existence of dtors with viable scope guards that are guaranteed to fire when exiting scope is reason enough for me to never write C and to look with a default skepticism on any codebase that thinks its developers are perfect enough not to need them.
The thing that is hard to replicate in C is destructors. Automatic deinitialization when leaving scope in very convenient. It allows you to have multiple exists from the scope without preceding each of them with prologue of dinit_*() calls or creating single exit point and jumping to it.
Coupling allocation with initialization is trivial to do without C++ constructors (which I find to be very poorly designed).
So you then have things like:
class Foo
{
public:
static Foo* create();
...
};
...
Foo* foo = Foo::create()
if ( foo != nullptr ) ...
> so how do you have Containers of foo?std::vector<Foo*>
Not saying either of those are better than the alternative (I prefer using exceptions and RAII), just pointing out what I've seen in real world projects.
class Foo
{
public:
Foo();
bool initialize(); // returns success
...
};
...
Foo foo;
if ( !foo->initialize() ) { // handle error }
This also means you can break up your initialization so that you drive the risky pieces from outside the object, rather than monolithically from within.This has a further benefit for testing, since you can use your major objects without fully initializing the entire world that they depend on.
It's also a technique that is widely used. See for example the cocos2d-x game library.
The benefit of such a technique is that you can then make the constructor private, making it impossible to create an object and not also call the initialize() method.
About the container issue: if you have objects that might fail during the creation it seems like a bad idea to allow things like:
std::vector<Foo> foos(10);
Having a separate initialization method which might fail - like proposed by others - is another option, but this means your objects need some kind of internal initialization state, and whenever you're handling such an object you never can be absolute sure that it's in a valid state.I'm quite a big fan of making invalid state not representable in an object and handling failure cases as early as possible.
What the create method returns depends heavily on your use case. If the returned objects can always be allocated on the heap, then a pointer or unique_ptr can be returned.
Using a separate initialize member function means that you may have objects in a zombie state laying around after a failed construction which lead to all kind of initialization order issues (you might get a pointer to the object, but is it initialized?). Also you need to remember to check the return type, which also need to be meaningful (does it return false on failure? or it returns 0 on success?).
Two phase initialization is a known antipattern which is, unfortunately, widely used and lead to all kind of pains.
Friends do not let friends use 2PI.
edit: sorry, I misread your comment, you were referring to the static function returning a pointer, which as you note is almost the same as the optional version. It forces heap allocation though, which is bad.
class Foo
{
public:
static Foo* create()
{
Foo* result = new Foo(); //exceptions disabled so new can return nullptr
if ( result )
{
//configure result here
}
return result;
}
private:
Foo() {}
};
...
Foo* badFoo = new Foo(); // compiler error because Foo() is private
Foo* foo = Foo::create(); //all good, no 2PI and can't forget to call initialize code
if ( foo ) //check for non-null, note, if using an optional you'd also need a similar check
{
...
}
Now the only way to create a Foo object is through the create() function and there is no separated initialize - it all happens in the same place.This pattern of using a static create method is explicitly designed to avoid 2PI and is very common, especially in codebases that disable exceptions.
Also note that I'm not personally advocating using it, just that it is commonly used to avoid 2PI.
The examples that come immediately to mind are the Publiser, Subscriber, and Timer classes that are part of the ROS C++ API: http://docs.ros.org/api/roscpp/html/classros_1_1Publisher.ht...
I agree that there are caveats with it, but I get nervous when people toss around a phrase like "known antipattern" with such confidence.
It does, and it can be, but in situations where it matters, the static create function typically returns a value from a preallocated pool of memory, so objects are all contiguous and cache friendly.
You can have contiguous Foos, but not in a vector. You can either have another static function to return an array of Foos, or more commonly have some sort of pool allocator and have the create function allocate objects from the pool.
Anyway, yes, there are limitations for using this pattern, so like all things it's a matter of weighing up the tradeoffs.
There are definitely things to be aware of before adopting such a pattern, or when trying to optimize code that uses it.