5 ms·
I've written my own Go (subset + extensions) -> C++ transpiler and using it on a game project: https://www.youtube.com/watch?v=8He97Sl9iy0 https://www.youtube.c
by nikki93 5y ago
I've written my own Go (subset + extensions) -> C++ transpiler and using it on a game project: https://www.youtube.com/watch?v=8He97Sl9iy0 https://www.youtube.com/watch?v=8He97Sl9iy0 -- No GC, it does have slices and has access to an entity/component API and with that I think you're basically set and don't need GC for games.
Example transpiler input / output: https://github.com/nikki93/gx/blob/master/example/main.gx.go?ts=2 https://github.com/nikki93/gx/blob/master/example/main.gx.go... becomes https://gist.github.com/nikki93/97ff376abb6718427387bb9cca2f548a https://gist.github.com/nikki93/97ff376abb6718427387bb9cca2f... Can call to C/C++ (including templates) w/o overhead.
That said, for logic that is heavy on async and escaping closures like how a lot of Go server code tends to be, a GC is maybe a reasonable tradeoff?
- dikei 5y agoNot trying to belittle you or anything, but I have a hard time understand why someone would use Go without Goroutine and Channel.
- grey-area 5y agoIf you want to compare languages on features, lack of complexity is also a feature.
- kaba0 5y agoBut lack of proper abstractibility, expressivity is not a “feature”.
- jhoechtl 5y agoAfter all, abstraction is a layer of indirection, hiding meaning. What is a "proper abstraction"?
- kaba0 5y agoFeel free to add together the set of a set of an empty set and a set of a set of a set of an empty set, but I prefer calling it 2+3. Or feel free to manipulate the voltage in some wire, and make sure that it is reliably understood at the other side as the same bit pattern you sent, but I prefer issuing an HTTP packet. These are all abstractions, hell, there is no field building as much on abstractions as IT does. We have to be on like 8-9 levels of abstraction to even do anything non-trivial.
- grey-area 5y agoLack of other people's abstractions is a feature too :)
- kaba0 5y agoIf it is a bad abstraction, sure. But a good abstraction is much much easier to understand then God knows how many lines of code with God knows what program flow.
- deleted 5y ago[deleted]
- nikki93 5y agoI don't think goroutines / channels is a clear win or even that relevant for data-oriented gameplay code. I go into this more in-depth in this comment: https://www.reddit.com/r/golang/comments/r2795t/i_wrote_a_simple_goc_compiler_to_use_for_gameplay/hm93ycc https://www.reddit.com/r/golang/comments/r2795t/i_wrote_a_si... (it addresses a bunch of points) This is what the game code looks like: https://gist.github.com/nikki93/0425d9ead9eb7810075434d006f3b790?ts=2 https://gist.github.com/nikki93/0425d9ead9eb7810075434d006f3... -- I don't think CSP helps much to improve on that while keeping serializability of state.
- nikki93 5y agoI'll read your statement as a question and answer that. I was using C++ for data-oriented gameplay coding, and I was interested in exploring making a language frontend that compiles to it to clean it up, as a side project (lots of dark corners to run into with C++, and I collected some experience on what those were since I'm managing a C++ game engine codebase at work). I needed a core that was basically a cleaned up C, which is what the C-ish core of Go is (the part other than goroutines, channels and GC), and Go has a good parser and typechecker library you can use. Goroutine and channel are cool for distributed server code or whatever, but not actually that useful for game programming. The main thing is having structs, procedures, some nice ergonomics over those (slices, type inference, non-escaping lambdas, occasional generics) and then metaprogramming so you can reflect over the data structures and have serialization and inspector UI. These are the elements actually relevant to game programming.
- jhgb 5y ago> I needed a core that was basically a cleaned up C Why didn't you consider D in 'Better C' mode? (https://dlang.org/spec/betterc.html https://dlang.org/spec/betterc.html) Not only does it already exist but with very high likelihood is more polished (by virtue of the man-years already invested into D) than a single person's ad-hoc compiler of a subset of Go to C++ could probably be. Unless of course you absolutely needed to use some pre-existing Go code...
- nikki93 5y agoI actually tried D as part of this exploration. I forget but it was confusing which of dmd or the llvm one to use, and ultimately the language ergonomics were not that much of an improvement over C++. Like "auto" is still super weird. I'm going for an actual improvement here, not another botched but slightly improved thing from the past. I think D's tooling worked the least stable-ly out of the box of everything I tried. It wasn't promising. That said, D was one of the things I explored the semantics of for ideas, and it's good that it tries to do the metaprogramming stuff. UFCS is also ok but needing to decide foo(o) and o.foo() at each callsite is actually mental overhead vs. the choice being decided in Go (I wrote the whole engine and game in Nim before so I have experience with this). Just because something has a bunch of years in it doesn't mean it's a good idea for a specific context. The transpiler I have here is just 1500 lines of code and captures all the semantics it currently supports. It uses Go's parser and typechecker from the stdlib and feels on the whole more polished than D as a result (generics are definition-checked, Go's package / module system just work, all the existing Go editor support and godoc etc. just work, ...). It's much easier and straightforward to metaprogram by just editing this simple piece of logic than squeezing it into language features (I've also done the same engine in Nim, explored in Zig, and written it once over in C++). I can, for example, make it so if you mark a function a certain way, it's also compiled to GLSL and useable as a shader (with structs shared). Or make it so types marked a certain way have all their pointers reference counted. There's way more control in this scenario, and the point is to have control to take matters into one's own hands and actually improve things.
- dgellow 5y agoGo without goroutines is still the same simple, straightforward language that people love to work with. The vast majority of Go code being written doesn't use goroutines.
- kevingadd 5y ago> I think you're basically set and don't need GC for games. This is kind of a semantics question, I think. The vast majority of games need some sort of lifetime management, and it's just a question of who does the lifetime management and what mechanism you use for it - refcounting, a mark/sweep GC, freeing everything at certain points of time, arenas, etc. If you're using an entity/component system to manage lifetime you have a GC - you wrote it. In my experience shipping games in C# compared to shipping them in C/C++ - you can do everything without the GC touching your stuff if you're really dedicated, but it's often not worth the trouble considering that any modern GC can handle scattered temporary per-frame allocations for you no problem with very minimal pause times, as long as you're thoughtful about it and the set of objects it needs to walk isn't too big. For example, if your data structures mix native data with pointers to object instances, a GC will have to sweep all of that data - splitting texture references out of a big table of draw calls means that the draw calls are now pure data and they don't need to be swept by a GC. Personally I prefer always having access to a GC because it means code that doesn't need careful lifetime management can be simpler to write and doesn't have issues like double-frees hiding inside it - things like automated tests, configuration UIs, debug consoles, and things you run once at startup or when loading a level. You can often go back and optimize some of this stuff later, too - for example LINQ is a notoriously messy feature in .NET's standard library that allocates tons of short-lived garbage, but the compiler makes it possible to replace all those LINQ data structures with non-allocating ones without having to rewrite your queries - but doing that moves costs elsewhere. If you're getting specifically harassed by pause times you're likely going to be paying costs with other systems, like if you use refcounting any time you touch that refcount you're burning cpu cycles and pushing other stuff out of cache (and the refcounting gets much more expensive if you have to use atomics for thread safety).
- nikki93 5y agoYup, pretty much. I do think that "GC" usually these days refers to a mark and sweep GC (certainly in the context of a discussion about Go?) or at most a refcount kind of thing applied to all reference-like language entities, not "any lifetime management system," the way I see folks use that term. But yeah I've found that the entity component data structure is a good lifetime management system very well suited to the game scenario, so there's no need for a different / more complex thing. And this is an exploration in how that + a simple / ergonomic language around it (along with growable arrays (slices)) pan out when making games in practice. There's no manual free calls or lifetime management anywhere in the code. Re: "using GC but then needing to / being thoughtful to make sure it's going ok" -- that's the thing. It seems better to not have to need to think about it, by having a system that's better suited to the thing you are working on. You also don't need to "often go back and optimize some of this stuff later too" because it just already has good performance with the straightforward code, and you don't need to add complexity. "splitting references out / pure data" -- that is indeed what the language nudges you to do by only having pure data. Essentially: yes, you can achieve the desired thing with intentionality and extra cognition in a different system (the same was true with other kinds of cognitive overhead in C++) and this is an exploration in developing a language + tools that focus on and bias toward the desired thing by default. Like I'm imagining folks getting started with gamedev using this + the integrated tooling and internalizing the practices you're talking about (that's a stretch vision, the current scope is to just build and test it in the context of one specific game project). I'll be releasing this engine + an example game with it soon, but here's what the code for this main game project (the one in the video) looks like (all the components, the top-level game loop, and then some example game logic): https://gist.github.com/nikki93/0425d9ead9eb7810075434d006f3b790?ts=2 https://gist.github.com/nikki93/0425d9ead9eb7810075434d006f3... It's just data structures, and then functions that do gameplay stuff on them. No lifetime management.