Skip to content

Instantly share code, notes, and snippets.

@reduz
Last active March 2, 2024 11:10
Show Gist options
  • Star 122 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save reduz/cb05fe96079e46785f08a79ec3b0ef21 to your computer and use it in GitHub Desktop.
Save reduz/cb05fe96079e46785f08a79ec3b0ef21 to your computer and use it in GitHub Desktop.

During the past days, this great article by Sam Pruden has been making the rounds around the gamedev community. While the article provides an in-depth analysis, its a bit easy to miss the point and exert the wrong conclusions from it. As such, and in many cases, users unfamiliar with Godot internals have used it points such as following:

  • Godot C# support is inefficient
  • Godot API and binding system is designed around GDScript
  • Godot is not production ready

In this brief article, I will shed a bit more light about how the Godot binding system works and some detail on the Godot architecture. This should hopefully help understand many of the technical decisions behind it.

Built-in Types

Compared to other game engines, Godot is designed with a relatively high level data model in mind. At the heart, it uses several datatypes across the whole engine. These datatypes are:

  • Nil: To indicate an empty value.
  • Bool, Int64 and Float64: For scalar math.
  • String: For String and Unicode handling.
  • Vector2, Vector2i, Rect2, Rect2i, Transform2D: For 2D Vector math.
  • Vector3, Vector4, Quaternion, AABB, Plane, Projection, Basis, Transform3D: For 3D Vector math.
  • Color: For color space math.
  • StringName: For fast processing of Unique IDs (internally a unique pointer).
  • NodePath: For referencing paths between nodes in the Scene Tree.
  • RID: Resource ID for referencing a resource inside a server.
  • Object: An instance of a class.
  • Callable: A generic function pointer.
  • Signal: A signal (see Godot docs).
  • Dictionary: A generic dictionary (can contain any of these datatypes as either key or value).
  • Array: A generic array (can contain any of these datatypes).
  • PackedByteArray, PackedInt32Array, PackedInt64Array, PackedFloatArray, PackedDoubleArray: Scalar packed arrays.
  • PackedVector2Array, PackedVector3Array, PackedColorarray: Vector packed arrays.
  • PackedStringArray: Packed string array.

Does this mean that anything you do in Godot has to use these datatypes? Absolutely not. These datatypes have several roles in Godot:

  • Storage: Any of these datatypes can be saved to disk and loaded back very efficiently.
  • Transfer: These datatypes can be very efficiently marshalled and compressed for transfer over a network.
  • Introspection: Objects in Godot can only expose their properties as any of those datatypes.
  • Editing: When editing any object in Godot, it is done via any of these datatypes (of course, different editors can exist for the same datatype, depending on the context).
  • Languge API: Godot exposes its API to all languages it binds via those datatypes.

Of course, if you are absolutely unfamliar to Godot, the first questions that come to mind are:

  • How do you expose more complex datatypes?
  • What about other datatypes such as int16?

In general, you can expose more complex datatypes via Object API, so this is not much of an issue. Additionally, modern processors all have at minimum 64 bit buses, so exposing anything other than 64 bit scalar types makes no sense.

If you are unfamliar to Godot, I can totally understand the disbelief. But in truth, it works fine and it makes everything far simpler at the time of developing the engine. This data model is one of the main reasons why Godot is such a tiny, efficient and yet feature packed engine compared to the large mainstream mamooths. As you get more familiar with the source code, you will start to see why.

Language Binding System

Now that we have our data model, Godot imposes a strict requirement that almost any function exposed to the engine API must be done via those datatypes. Any function parameters, return types or properties exposed must be via them too.

This makes the job of the binder much simpler. As such, Godot has what we call an universal binder. How does this binder work, then?

Godot registers any C++ function to the binder like this:

Vector3 MyClass::my_function(const Vector3& p_argname) {
   //..//
}

// Then, on a special function, Godot does:

// Describe the method as having a name and the name of the argument, the pass the method pointer
ClassDB::bind_method(D_METHOD("my_function","my_argname"), &MyClass::my_function);

Internally, my_function and my_argument are converted to a StringName (described above), so from now onwards they are treated just as a unique pointer by the binding API. In fact, when compiling on release, the argument name is ignored by the template and no code is generated, since it serves no purpose.

So, what does ClassDB::bind_method do? If you want to dive into the depths of insanity and try to understand the incredibly complex and optimized C++17 variadic templates black magic, feel free to go ahead.

But In short, it creates a static function like this, which Godot calls "ptrcall" form.:

// Not really done like this, but simplifying as much as possible so you get an idea:

static void my_function_ptrcall(void *instance, void **arguments, void *ret_value) {
    MyClass *c = (MyClass*)instance;
    Vector3 *ret = (Vector3*)ret_value;
    *ret = c->my_method( *(Vector3*)arguments[0] );
}

This wrapper is basically as efficient as it can be. In fact, for critical functions, inline is forced into the class method, resulting in a C function pointer to the actual function code.

Then Language API works by allowing the request of any engine function in "ptrcall" format. To call this format, the language must:

  • Allocate a bit of stack (basically just adjusting the stack pointer of the CPU)
  • set a pointer to the arguments (which already exist in native form in this language 1:1, be it GodotCPP, C#, Rust, etc).
  • call.

And that's it. This is an incredibly efficient generic glue API that you can use to expose any language to Godot efficiently.

So, as you can imagine, the C# API in Godot basically uses a C function pointer via unsafe API to call after assigning pointers to native C# types. It is very, very efficient.

Godot is not the new Unity - The anatomy of a Godot API call

I want to insist that the article written by Sam Pruden is fantastic, but if you are not familiar with how Godot is intended to work under the hood it can be very misleading. I will proceed to explain a bit more in detail what is easy to misunderstand.

Only a pathological use case is shown, the rest of the API is fine.

The use case shown in the article, the ray_cast function, is a pathological one in the Godot API. Cases like this are most likely less 0.01% of the API exposed by Godot. It looks like the author found this by coincidence when trying to profile raycasting, but it is not representative of the rest of the bindings.

The problem is that, at the C++ level, this function takes a struct pointer for performance. But at the language binding API this is difficult to expose properly. This is very old code (dating to the opensourcing of Godot) and a Dictionary was hacked-in to use temporarily until something better is found. Of course, other stuff was more prioritary and very few games need thousands of raycasts, so pretty much nobody complained. Still, there is a recently open proposal to discuss more efficient binding of these types of functions.

Additionally, to add to how unfortunate this choice of function is, the Godot language binding system does support struct pointers like this. GodotCPP and Rust bindings can use pointers to structs without any issue. The problem is that C# support in Godot predates the extension system and it was not converted to it yet. Eventually, C# will be moved to the universal extension system and this will allow the unifying of the default and .net editors, it is just not the case yet, but its top in the list of priorities.

The workaround is even more pathological

Although this time, due to a limitation of C#. If you bind C++ to C#, you need to create a C# version of a C++ instance as an adapter. This is not an unique problem to Godot, any other engine or application doing this will require the same.

Why is it troublesome? because C# has a garbage collector and C++ does not. This forces the C++ instance to keep a link to the C# instance to avoid it from being collected.

Due to this, the C# binder must do extra work when calling Godot functions that take class instances. You can see this code in Sam's article:

public static GodotObject UnmanagedGetManaged(IntPtr unmanaged)
{
    if (unmanaged == IntPtr.Zero) return null;

    IntPtr intPtr = NativeFuncs.godotsharp_internal_unmanaged_get_script_instance_managed(unmanaged, out var r_has_cs_script_instance);
    if (intPtr != IntPtr.Zero) return (GodotObject)GCHandle.FromIntPtr(intPtr).Target;
    if (r_has_cs_script_instance.ToBool()) return null;

    intPtr = NativeFuncs.godotsharp_internal_unmanaged_get_instance_binding_managed(unmanaged);
    object obj = ((intPtr != IntPtr.Zero) ? GCHandle.FromIntPtr(intPtr).Target : null);
    if (obj != null) return (GodotObject)obj;

    intPtr = NativeFuncs.godotsharp_internal_unmanaged_instance_binding_create_managed(unmanaged, intPtr);
    if (!(intPtr != IntPtr.Zero)) return null;

    return (GodotObject)GCHandle.FromIntPtr(intPtr).Target;
}

While very efficient, it's still not ideal for hot paths so the Godot API exposed is considerate and does not expose anything critical this way. The workaround used, however, is quite complex and hits this path due to not using the actual function intended for it.

The question of cherry picking

I firmly believe the author did not cherry pick this API on purpose. In fact, he himself writes that he checked other places of API usages and did not find anything with this level of pathology either.

To clarify further, he mentions:

Let’s also remember that Dictionary is only part of the problem. If we look a little wider for things returning 
Godot.Collections.Array<T> (remember: heap allocated, contents as Variant) we find lots from physics, 
mesh & geometry manipulation, navigation, tilemaps, rendering, and more.

From my side and contributors side, none of those usages are hot paths or pathological. Remember that, as I mentioned above, Godot uses the Godot types mainly for serialization and API communication. While it is true that they do heap allocation, this only happens once when the data is created.

I think what may have confused Sam and a few others in this area (which is normal if you are not familiar with the Godot codebase) is that Godot containers don't work like STL containers. Because they are used mainly to pass data around, they are allocated once and then kept via reference counting.

This means, the function that reads your mesh data from disk is the only one doing the allocation, then this pointer gets passed through many layers via reference counting until arrives Vulkan and is uploaded to the GPU. Zero copies happen along the way.

Likewise, when these containers are exposed to C# via the Godot collections, they are also reference counted internally. If you create one of those arrays to pass the the Godot API, the allocation only happens once. Then no further copies happen and the data arrives intact to the consumer.

Of course, intenally, Godot uses far more optimized containers that are not directly exposed to the binder API.

Misleading conclusion

The article concludes like this:

Godot has made a philosophical decision to be slow. The only practical way to interact with the engine is via this binding layer, and its core design prevents it from ever being fast. No amount of optimising the implementation of Dictionary or speeding up the physics engine is going to get around the fact we’re passing large heap allocated values around when we should be dealing with tiny structs. While C# and GDScript APIs remain synchronised, this will always hold the engine back.

As you have read in the above points, the binding layer is absolutely not slow. What can be slow is an extremely limited amount of use cases that can be pathological. For those cases, a dedicated solution is found. This is a general philosophy behind Godot development that helps keep the codebase small, tidy, maintainable and easy to understand.

In other words, this principle:

image

The current binder serves its purpose and works well and efficiently for over 99.99% of use cases. For the exceptional ones, as mentioned before, the extension API supports structs already (which you can see here in this excerpt of the extension api dump):

		{
			"name": "PhysicsServer2DExtensionRayResult",
			"format": "Vector2 position;Vector2 normal;RID rid;ObjectID collider_id;Object *collider;int shape"
		},
		{
			"name": "PhysicsServer2DExtensionShapeRestInfo",
			"format": "Vector2 point;Vector2 normal;RID rid;ObjectID collider_id;int shape;Vector2 linear_velocity"
		},
		{
			"name": "PhysicsServer2DExtensionShapeResult",
			"format": "RID rid;ObjectID collider_id;Object *collider;int shape"
		},
		{
			"name": "PhysicsServer3DExtensionMotionCollision",
			"format": "Vector3 position;Vector3 normal;Vector3 collider_velocity;Vector3 collider_angular_velocity;real_t depth;int local_shape;ObjectID collider_id;RID collider;int collider_shape"
		},
		{
			"name": "PhysicsServer3DExtensionMotionResult",
			"format": "Vector3 travel;Vector3 remainder;real_t collision_depth;real_t collision_safe_fraction;real_t collision_unsafe_fraction;PhysicsServer3DExtensionMotionCollision collisions[32];int collision_count"
		},

So, ultimately, I believe that the conclusion that "Godot is slow by design" is a bit rushed. What is currently missing is the move of the C# language to the GDExtension system in order to be able to take advantage of these. This is currently a work in progress.

To sum up

I hope that this short article is used to dispell a few misconceptions that unintentionally arised from Sam's excellent article:

  • Godot C# API is inefficient: This is absolutely not the case, but very few pathological cases remain to be solved and were already being in discussion before last week. In practice, very very few games may run into them and, by next year, hopefully none.
  • Godot API is designed around GDScript: This is also not true. In fact, until Godot 4.1, typed GDScript did calls via "ptrcall" syntax, and the argument encoding was a bottleneck. As a result, we created a special path for GDScript to call more efficiently.

Thanks for reading and remember that Godot is not commercial software developed behind closed doors. All of us who make it are available online in the same communities as you. If you have any doubt, feel free to ask us directly.

Bonus: As a side note, and contrary to popular belief, the Godot data model was not created for GDScript. Originaly, the engine used other languages such as Lua or Squirrel, with several published games while an in-house engine. GDScript was developed afterwards.

@jams3223
Copy link

I want to say that I have looked at Godot's source code, and their code is very based on simplicity and complexity where performance is needed. There's nothing wrong with it as it has the advantage of gaining more performance than Unity could ever give you. There's not a lot of things that are slowing it down. I would say the engine in terms of quality is getting on par with Unity 2022 and if these issues are fixed could even give x2 the performance gain that unity has.

@reduz Juan I am surprised at the amount of work that you've put in with the little amount of resources that the team had; this shows me that this is a passion project foremost. You're coding philosophy has nothing wrong because it pushes us to fix simple problems without getting into the core of the engine and complicating things while fixing complex problems by going back to the drawing board to make things even better.

@jams3223
Copy link

@reduz I am particularly talking about GDNative/3.x don't know if GDExtension/4.x is any different, but if my memory is correct, im talking about how all the calls must go through godot_method_bind_ptrcall() which needs to have its arguments packed into an array of void* to a MethodBind virtual class, which then gets dispatched and each void ptr is forwarded as a variant to the actual call parameters.

When I had to implement that it left my head scratching as of why it was made that way, when all I expected was: c_funcptr(cStruct);

The cost may be small, but its there and I don't see why it has to exist (and it even adds complexity!?).

Like I remember around 3.0 to 3.1 cpp gdnative compiled dynamic libraries getting 5 times bigger, and the reason was that the function calling wrappers at __icalls.hpp got inlined.

You guys should focus on version 4, as it is an improvement to Godot overall. Many optimizations have been made to improve the engine, and it's getting even better. If it were 4 years ago, I would say Godot wouldn't be ready for mainstream use, but now it's ready, and it's time to get our feet wet.

@reduz
Copy link
Author

reduz commented Sep 23, 2023

A bit offtopic, but as we are talking performance, this video is quite interesting:
https://www.youtube.com/watch?v=8v5SrgkC_dI

This is a benchmark that tests API overhead and rendering.

I can't draw any conclusion because I don't know how Unity works under the hood, but it would appear that by default, Godot with C# under a stress tests holds its own better than Unity with Mono. Unity needs to use ECS here to be at the same performance as Godot.

@emrys90
Copy link

emrys90 commented Sep 23, 2023

A bit offtopic, but as we are talking performance, this video is quite interesting: https://www.youtube.com/watch?v=8v5SrgkC_dI

This is a benchmark that tests API overhead and rendering.

I can't draw any conclusion because I don't know how Unity works under the hood, but it would appear that by default, Godot with C# under a stress tests holds its own better than Unity with Mono. Unity needs to use ECS here to be at the same performance as Godot.

What about when compared with IL2CPP? As from what I am aware, Godot does not support NativeAOT yet?

Does Godot have an incremental garbage collector?

@reduz
Copy link
Author

reduz commented Sep 23, 2023

@emrys90 I don't know, but as far as I understand the video uses release builds. Godot does not have a GC internally, that is up to C# to manage, so ultimately you only collect stuff you create yourself on the C# side in Godot.

@jams3223
Copy link

jams3223 commented Sep 23, 2023

@emrys90 I don't know, but as far as I understand the video uses release builds. Godot does not have a GC internally, that is up to C# to manage, so ultimately you only collect stuff you create yourself on the C# side in Godot.

Will it be possible to make gdscript faster than c# in the future.

@emrys90
Copy link

emrys90 commented Sep 23, 2023

@emrys90 I don't know, but as far as I understand the video uses release builds. Godot does not have a GC internally, that is up to C# to manage, so ultimately you only collect stuff you create yourself on the C# side in Godot.

Release builds can be Mono or IL2CPP, which is faster than Mono and is what most mobile projects/etc would use.

Is that a performance benefit in Unity's favor then for the GC? The incremental GC allows you to not have to worry so much about instantiating classes/etc as it won't freeze the frame for long when it eventually triggers a collection.

@reduz
Copy link
Author

reduz commented Sep 23, 2023

@jams3223

Will it be possible to make gdscript faster than c# in the future.

It can be made faster with a JIT and that is definitely planned, doubt it can get as fast as C# because its a primarily dynamically typed language.

@emrys90

Is that a performance benefit in Unity's favor then for the GC? The incremental GC allows you to not have to worry so much about instantiating classes/etc as it won't freeze the frame for long when it eventually triggers a collection.

On the side of Godot managing its own allocations, no, that's definitely in Godot favor because it never goes through a GC.
Regarding the incremental GC, as far as I understand the one Microsoft uses in current C# (what Godot uses) is far better than the one Unity uses, but this really not my area of expertise so I can't comment more than what I've heard from others.

@emrys90
Copy link

emrys90 commented Sep 23, 2023

Okay, thank you for the information!

@thimenesup
Copy link

@reduz A bit offtopic, but as we are talking performance, this video is quite interesting: https://www.youtube.com/watch?v=8v5SrgkC_dI
This is a benchmark that tests API overhead and rendering.

No source available makes the video benchmark useless, and also the rendering differs quite a bit between both, may be due to doing blending I think? Which could definitely be a bottleneck... specially because there are no hardware specs listed either and the GPU could be a low end one choking on fill rate.

@reduz
Copy link
Author

reduz commented Sep 23, 2023

@thimenesup Yeah, I really wish I had source code to compare. Godot does blending in all 2D drawing though, probably its set up different in Unity, but it should not make much of a difference. Also Godot is probably far more efficient at drawing 2D than Unity (since in Godot 2D is a very dedicated pipeline, while Unity does it over 3D), which might be adding a penalty.

@markdibarry
Copy link

markdibarry commented Sep 23, 2023

Just some musing, but I find the GC lag in C# with Godot to be just as noticeable as the shader lag when each is loaded for the first time. Unity uses an incremental GC specifically so (at the cost of some upfront performance) your users have a smoother gaming experience by it collecting smaller amounts more often, rather than seemingly random abrupt stuttering (like listening to an old CD) when it comes time to collect the warehouse of garbage you've saved up. It's not an "if", but "when", and since the GC is going to stop everything no matter what when it's trash day, then while it happening less often is better, it happening at all is the real problem.

While I agree that there are some optimizations to be made to prevent more allocations than necessary and bits of performance, I'm not sure how much of it can be mitigated just from the architecture that using Godot encourages. For example: if you have a stat system in your game, you probably would want to be able to construct new enemies with different stats in the editor. To do so, means that your stats have an array of stat objects, which may have otherwise been a struct, is now a Resource, but also now have a penalty when using because they need to be in a Godot.Collections.Array<Modifier>. And maybe an array of modifiers for your stats? Those now must be Resources as well in Godot Collections, and what about conditions on those modifiers for status effects? Now every modifier in the collection needs a Godot.Collections.Array<Condition>, and all of these need to be Resources as well.

Of course, none of this is in a hotpath, and is an example of things that would be allocations anyway. However, just because you want to set it up in the editor, it's still a performance hit every time you need to interact with it, which wouldn't be weird to be hundreds to thousands of times per frame with just ten enemies on screen. When dealing with things that can trigger a domino effect, the individual calls add up. But it's hard to say without benchmarks.

@reduz
Copy link
Author

reduz commented Sep 23, 2023

@markdibarry I am unfamiliar with the Microsoft C# compiler and runtime. All I can tell you from my experience is that Incremental GCs have the problem that if you are careless with your allocations every frame, you can actually cause huge memory growth, and this is a problem.
I am pretty sure a lot of people make games in C# using the Microsoft runtime, so there must be good info about how to tune it.

Although folks to be entirely honest, it sounds to me like using C# for some of the things you do is like trying to push a rock uphill. Is it not better to just use C++, which is well supported in Godot, almost as easy to use since it has has the same API and you don't have to care about the GC at all?

@PavelCibulka
Copy link

I agree that in most cases it would be better to just use C++ instead of C#.

There is masive benefit of C# in modding capability. You can use static readonly variable and load them from file at the start. C# JIT than make same optimization as if they were CONST in AOT langauge.

Example 1
static readonly flaot buildingCostCoef;
in config set to 1.0:
JIT will completelly remove any code that multiply value by this variable
in config set to 0:
JIT will change code to set to 0 instead of multiply value by this variable (and continue with cascade optimization triggered by this change)

static readonly bool enableWeatherSystem
code: if(enableWeatherSystem) {do something}
in config set to true:
JIT modify all source to just {do something} without condition check
in config set to false:
JIT modify all source and completly remove whole if and inside code

This can be used also for logging and debugging.
static readonly bool verifyData
code: if(verifyData) {verifyData / log / make memory dump if problem}

@reduz
Copy link
Author

reduz commented Sep 24, 2023

@Nifflas

Godot does not generate any garbage during the runtime because it does not use a GC internally. I was making more a comment regarding to having to be so careful on the C# side of things, outside the engine API, and the comment more out of curiosity than anything else since I am not familiar with this.

@Nifflas
Copy link

Nifflas commented Sep 24, 2023

Sorry, I removed that before you responded. I feel like I haven't read up on Godot enough.

I know the Godot engine, GDScript and C++ doesn't have a garbage collector, but I intend to use C#. Isn't the stuff in https://sampruden.github.io/posts/godot-is-not-the-new-unity/ true tho? That if you use C# with Godot, some features creates new arrays and managed classes, despite it's things you call every frame?

@reduz
Copy link
Author

reduz commented Sep 24, 2023

@Nifflas I tried to refute this as best as possible in the article above. Some features indeed create new arrays or classes that are managed on C#, but these are not intended to be something you call every frame. At most, some of these calls exist so you can keep a reference to the data returned until you no longer need it.

@reduz
Copy link
Author

reduz commented Sep 24, 2023

Keep in mind there are thousands of people contributing to Godot, some really experienced industry veterans. There is a lot of care on how APIs are exposed. The problem described in the article you linked are known but depend on other areas being worked on until they can be fixed, so they will take a bit more. Yet they are a dozen cases of this at much in the whole codebase.

@Nifflas
Copy link

Nifflas commented Sep 24, 2023

Yeah, that's fair! Maybe I overreacted. If I know I can use the C# stuff in the future and feel safe that there will be 0 bytes of garbage from all features I use on a per-frame basis, I won't worry at all! It needs to be a guiding principle, same requirement as what I expect from Unity or things from its asset store basically.

@markdibarry
Copy link

Is it not better to just use C++, which is well supported in Godot, almost as easy to use since it has has the same API and you don't have to care about the GC at all?

Maybe? If you're a seasoned C++ developer, it may make more sense to use C++ than anything else for workflow, memory management, etc. Obviously C# and GDScript are completely separate languages with tons of differences that appeal to different users, but I think the appeal of C# on the front of memory management from a user perspective is the same one as users of JavaScript, Python, and GDScript which you designed to have the same appeal: You shouldn't have to think about it. It just works!

I know you said you're not as familiar with C#, but I'm more a longtime Godot C# user than Unity, so I'm pretty used to discussing tricks the community has come up with to mitigate some of these issues. I imagine the concern comes from C# users coming from other game engines or a web backgrounds. In those scenarios, they don't need to be aware of the GC in their normal game code. For other game engines, it's just "don't do anything crazy in methods being used every frame", and web dev obviously wouldn't care about frame stuttering at all. It just works. To them, the burden is on the API side, not the user's. I mainly work on tools for others to use, so performance is more important to me because I have no idea what the needs are of the person who is using it. All I know is I don't want my code to be their bottleneck.

There's always going to be some culture shock when moving to a different country and learning the ways of the locals and whether or not you'll be accepted. Similarly, there will be concern from locals that outsiders will make them lose their culture. I can't speak on all the scenarios discussed, but I can at least hopefully shed some light on where some of the concern comes from. Honestly, I'm not too familiar with a lot of this since my day-to-day with C# is only Godot and my job, but as far back as 3.0 the Godot C# community's main rules are "never ever use Godot.Collections if you can avoid it" and "avoid doing anything that communicates with the engine whenever possible" (which actually encompasses the first rule). Which, there's been some improvements on that front since then, but it's still significant enough that you need to be aware of it.

If there's a solution where that wouldn't be the case anymore, that'd be nice?

@reduz
Copy link
Author

reduz commented Sep 24, 2023

Alright, I want to thank everyone hugely for all the feedback. I believe I now understand well what the problem is with C# and the garbage collector (which arised from the discussion here, not necessarily related to the original article).

While I do my best to give my word that we do our best to ensure hot paths don't do memory allocation, and that the general consensus that the GC in CoreCLR is better than Unity's GC regarding to this, I can totally understand that if anyone wants to make full commitment into using Godot, they should be given full assurance that they can avoid GC interaction at all.

As such, I opened this proposal regarding to no allocation and no copies versions for functions exposed in the C# API, so they can be used anytime you are concerned about performance / GC spikes:

godotengine/godot-proposals#7842

@perkele1989
Copy link

I think the concern regarding all of this drama isn't necessarily the implementation details. It's the philosophy Godot has embraced during its development over the years. Godot has chosen a path where flexibility and ease of use/quick implementations have taken precedence over maximizing performance. This has led to a situation where, if Godot wishes to compete with hyperoptimized game engines, they would basically have to rewrite everything from the ground up (which is practically quite unfeasible at this point).

Personally, I don't think it really needs to be a concern. Godot is clearly still an engine that's used for many smaller games, where performance isn't necessarily a key deciding factor. Clearly these devs have chosen Godot for its benefits and pros, not its limitations. And for them, clearly this hasn't been an issue.

The real issue, if there is one, is made visible once you start to consider Godot a serious alternative for AA or AAA level games. At this level, using Godot can easily become unfeasible due to its philosophical decisions. I'm not sure if Godot should or even wants to compete at this level though, so I'm not sure it's even an issue to begin with.

The engine we are building (e2) is built on an entirely different philosophy, but then again, we have an entirely different userbase that we wish to target. In fact, for a lot of teams/types of games, we would instead recommend something like Godot or Unity, due to the higher level of prerequisite experience that comes with using a more advanced and optimized engine.

Be glad that you aren't making Unreal, an engine that is supposedly "highperforming" and "hyperoptimized", however in reality it's extremely bloated and comes with very high performance overheads and tons of technical debt.

I definitely think Godot has a place for certain types of games, where it's the best alternative out there. Just as we are aiming to be the best alternative for a whole other types of games. Compared that to Unreal, which is an engine that is half-shitty for every type of game, even if it of course also has its benefits too.

My point is, don't worry too much about performance, it's not a key concern of Godot and honestly neither should it.

/end rant

@reduz
Copy link
Author

reduz commented Sep 26, 2023

@haikarainen

This has led to a situation where, if Godot wishes to compete with hyperoptimized game engines, they would basically have to rewrite everything from the ground up (which is practically quite unfeasible at this point).

You are basically describing all the optimization effort that hundreds of contributors put into Godot 4 these past three years. Thanks for the informed rant, regardless.

@perkele1989
Copy link

You are basically describing all the optimization effort that hundreds of contributors put into Godot 4 these past three years

To be very frank, if I were actually describing that effort, we wouldn't be having this discussion right now. The "practically unfeasible" point is quite important for you to either understand and accept, or if you actually believe Godot can compete at this level, prove wrong. The fact that you've put 3 years into optimizing this engine, and still today do things like what Sam brings up in his article, and argue about gameloop allocations being negligible, is probably the biggest evidence for it being actually unfeasible.

My recommendation here would be to pivot and embrace what Godot is good at. If you truly wish to compete with high performing engines, I'm convinced your best path forward would be to build an entirely new engine from scratch, as it will likely be faster to do than what you're attempting to do now.

Best,

@reduz
Copy link
Author

reduz commented Sep 26, 2023

@haikarainen

To be very frank, if I were actually describing that effort, we wouldn't be having this discussion right now

"This discussion" is about an isolated problem in the language binder layer. To divert the discussion towards general engine optimization is entirely missing the point.

@perkele1989
Copy link

"This discussion" is about an isolated problem in the language binder layer. To divert the discussion towards general engine optimization is entirely missing the point.

Your original gist was about an isolated problem. My comment, and your response to said comment, and thus "this discussion" we are having right now, while off topic, is not.

@reduz
Copy link
Author

reduz commented Sep 26, 2023

@haikarainen

Your original gist was about an isolated problem. My comment, and your response to said comment, and thus "this discussion" we are having right now, while off topic, is not.

Thanks for using ambiguity to make your point. Now that we agree that your original post is off-topic, what are you trying to accomplish with it?

@jams3223
Copy link

@haikarainen Godot 4 is not the same as Godot 3, it has already been rebuilt from the ground for 3D; in fact, it took 1 year, and next year is going to make it 2 years since Godot 4 dropped, and it's still in the stabilizing faze. Godot 3 sure isn't highly performant, but Godot 4 is and is getting better day by day.

@reduz
Copy link
Author

reduz commented Sep 26, 2023

Folks, I want to thank everyone for this super insightful and productive discussion. I gained a lot of understanding on how C# developers expect APIs to be exposed and should hopefully help improve this aspect of Godot.

To avoid spamming everyone else with notifications, and because locking is not possible on Gists, I ask to please not continue posting on this thread.

@TranscendentThots
Copy link

Since these 'pathological' use-cases are so vanishingly rare, can we please get a list of them in the documentation? I'd settle for something non-comprehensive, or even community maintained. Anything to help users switching from Unity to decide which "standard" video game features we should avoid planning a Godot game around, or which API calls to avoid like the plague during implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment