11 ms·
How to write better game libraries
- edem 7y agoPlease add (C) to the title of the article. In this form, it is misleading because most of the advice only applies to C.
- kd5bjo 7y agoThe target audience isn’t C programmers, but library authors that want their library to be usable with multiple programming languages. Most of the advice applies to C libraries because the article starts by advising you to write your library in C, and gives several reasons.
- rootlocus 7y agoThe (games) in the title pretty much limits the article to C / C++
- nurettin 7y ago>> C is the lingua franca of programming I don't have the exact numbers, but this seems debatable.
- Reelin 7y agoIt's the lingua franca in that it's the standard for interoperating. Every high level language I've used has had at least one C FFI readily available.
- nurettin 7y agoWe do have a mutual understanding on whether C foreign function interfaces are available for other programming languages, which does enable languages to send system requests (for systems which are written in C) or to create optimized C code to parse documents or calculate formulas. I am aware that the concept of making languages talk with C interfaces exist. And I made the above statement knowing this.
- rustybolt 7y agoI think the term lingua franca is most often used to indicate a commonly understood language, not one that everybody actively uses. Six of the eight most popular programming languages (from https://www.tiobe.com/tiobe-index/ https://www.tiobe.com/tiobe-index/) have C-like syntax. I think most people would agree. Edit: I think that in this context, Reelin's comment is more appropriate: This is not about understanding, but about interoperability.
- fortran90 7y agoSince this article lays so much emphasis on C, I have an honest question to everyone. What is a good way for a beginner to learn C in the current time? The minefield of undefined behavior is really overwhelming to a beginner. Are there any good resources that teach C the right way with good advice and best practices to navigate the UB minefield?
- kd5bjo 7y agoI can’t advise on modern resources as I’ve known C for a while now, but I can speak a bit about the mindset required. Programming in C isn’t much different than programming in any other language, just without the safety railings. If you’re at the stage where you are typically fixing bugs by understanding what is happening and then making a single targeted change to correct the issue, you should have no significant trouble working in a C codebase. If, on the other hand, you’re in the habit of shotgun debugging, where you make repeated changes to the problem code until it appears to work, you’re quite likely to leave behind various problems that will be hard to figure out. Often, experience is the best teacher. If you’re not exposing your program to malicious users (aka. the public at large), the most serious issue you’re likely to run into is either a program crash or data corruption— nothing that will really harm your computer, but may cause you grief as you try to figure it out. In that process, though, you’ll learn an awful lot about how everything works. So, go write some programs for your own use and see how they crash and burn so that the next thing you make is more stable. Eventually you’ll start to intuitively spot trouble before it actually happens.
- tick_tock_tick 7y agoBeing careful with malloc/free and using a modern toolchain that will warn on 99% of stuff is pretty much all you need. People way overemphasize UB
- pjmlp 7y agoYes, you need to dive into books like Writing Secure Code https://www.amazon.com/Writing-Secure-Second-Developer-Practices/dp/0735617228 https://www.amazon.com/Writing-Secure-Second-Developer-Pract... Secure Programming Cookbook for C and C++ http://shop.oreilly.com/product/9780596003944.do http://shop.oreilly.com/product/9780596003944.do SEI CERT C Coding Standard https://wiki.sei.cmu.edu/confluence/display/c https://wiki.sei.cmu.edu/confluence/display/c
- jandeboevrie 7y agoThis is bad advice on the c++ part. If you don't use RAII, then you could've just not done the c++ lib (and have just a c lib). Exceptions, raii and smart pointers are what makes modern c++ so pleasant, next to templates.
- a_t48 7y agoFWIW I’ve never been at a job that allows exceptions...but smart pointers and RAII are great.
- jstanley 7y agoNo exceptions. No exceptions.
- je42 7y agoExceptions are being fixed. See https://www.youtube.com/watch?v=os7cqJ5qlzo https://www.youtube.com/watch?v=os7cqJ5qlzo
- deleted 7y ago[deleted]
- EliRivers 7y agoOn the other side of the coin, I don't think I've ever had a job in which anyone would have ever considered banning exceptions (although I have worked at some where people did know that some other companies did ban them). The world of programming is a very broad church indeed.
- snovv_crash 7y agoOften bare-metal embedded toolchains don't support stack unwinding. I've even worked on an embedded linux where the toolchain didn't support stack unwinding. It made porting certain code to the platform interesting, to say the least.
- saagarjha 7y agoI don't see it mentioned, but it's important to note that you don't have to write your library in the same language that you expose to your users. You can write everything in C++/Rust/Python and expose something compatible with the C ABI and people will be able to use it (provided they have your toolchain…)
- pjmlp 7y agoFully agree with you. Anyone providing a library written in pure C, better be serious about security and prove that they have taken all the required steps to handle memory corruption and UB exploits. We really need more liability on software development.
- erikbye 7y agoDo note where this article resides. Casey Muratori (of Handmade Hero, a game dev tutorial project) is a self-proclaimed anything-not-C hater. A community has formed around his project, and has, of course, adopted his hard-line stance.
- pjmlp 7y agoInteresting, thanks for the hint.
- abnercoimbre 7y agoThe earlier comment was misinformed. Handmade Network is inspired by the series, yes, but stands on its own and embraces new languages and very different projects. See the Handmade conference[0] for examples. [0] https://handmade-seattle.com https://handmade-seattle.com
- sharpneli 7y agoIndeed. And a C style api is still valid C++ too. Nothing in C++ mandates one to design a boost-esque inheritance and template monstrosity. Functions and opaque handles are often way simpler for the user.
- Const-me 7y agoSome of these recommendations are awesome, others aren’t good, IMO. > Always prefix your names to avoid name collisions Solved by C++ namespaces. > Use header guards instead of #pragma once. #pragma once is supported by all mayor compilers, header guards can introduce bugs, also #pragma once builds measurably faster: https://github.com/electronicarts/EASTL/blob/3.15.00/include/EASTL/vector.h#L64-L66 https://github.com/electronicarts/EASTL/blob/3.15.00/include... > Expose constants to the user using constexpr variables. Modern C++ has strongly-typed scoped enums for such constants.
- kd5bjo 7y agoC++ features don’t help when you’re programming in straight C, as the article advises.
- pjmlp 7y agoOther than UNIX like kernels and tiny PIC micro-controllers no one else should still be programming in straight C in 21st century.
- jstimpfle 7y agoIf people choose to and it gives them joy, and they are productive and even learn valuable things, why not let them be?
- pjmlp 7y agoBecause if someone else happens to use that library, it might blow up on their face, e.g. WhatsApp dependency on android-gif-drawable. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11932 https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1193...
- deleted 7y ago[deleted]
- 7y ago
- astrobe_ 7y agoMake is a much the lingua franca of build systems as C is for programming, so there's no reason not to provide a (simple) makefile. The alternative they suggest is worse anyway.
- huhtenberg 7y agoWindows is the reason make is not a good choice. Visual Studio is a de-facto standard for Windows development and it's not exactly makefile-oriented. The lowest common denominator for build instructions that works everywhere is literally "set your include paths like so and compile these sources".
- astrobe_ 7y agoHow strange. I have tinkered with a couple of FOSS games of Windows and none of them required VS (not to mention another couple of other non-game projects). Maybe that's because they often are multi-platform projects so Make is the best choice at the global level for them? > The lowest common denominator for build instructions that works everywhere is literally "set your include paths like so and compile these sources". Yes, but that's quite inconvenient and error-prone ; a typo in a -D flag is very likely to not compile what you wanted. Make is not a multi-megabyte program with a frack tonne of dependencies. Having your users to install it in order to build your project is far from being completely outrageous (unless for coughmicrosoftcough cults that consider the command-line is evil).
- diamondo25 7y agoFunny thing is that AtmelStudio (for embedded development of Atmel/Microchip chips) is basically a VisualStudio shell with a makefile generator (built in c#). This is then pushed through make and invokes gcc...
- killmov3s 7y agobuild tools are a mistake in the majority of cases
- rienbdj 7y agoWhy not a C++ library with an optional C API?
- LessDmesg 7y agoBecause C++ is one of the most complex languages in the world, hence a pain to interop with. C FFIs, on the other hand, are ubiquitous.
- izacus 7y agoThe idea (and one that worked well in several projects I've worked on) is that you use C++ as main language of the library and make outside bindings C. It does require some discipline, but C++ has many portable constructs that make life significantly easier when writing business logic.
- pnako 7y agoIt's possible to use the "hourglass" pattern where your implementation is written in C++, you export a C API (and ABI), and then you can provide an additional, light C++ wrapper based on the C API.
- shmerl 7y ago> Write the library in Standard C99 Why not write it in Rust, and provide C99 bindings? Using stock C for the actual library doesn't sound like a good idea to me, when today there are better alternatives. And for sure avoid using Metal lock-in directly. Use Vulkan, which Apple should have supported from the beginning. For lock-in targets, there are translation options from Vulkan.
- LucaSas 7y agoHi, the reason for using C99 is that it is fully compatible with other languages. If you write a library in Rust you might be tempted to use Rust only feature which will then make it hard to wrap the library for other languages. And if you don't use those features from Rust in your library people will comment that your library isn't Rust enough. Same thing applies to C++. To that extent my advice is to write C in C rather than C in Rust or C++. Also if people don't have a Rust toolchain setup, compiling the library and using it from source would be hard. In some cases integrating Rust in their toolchain could be hard. Regarding metal and vulkan I will edit the article to mention Vulkan there too alongside metal. Thanks for your feedback.
- shmerl 7y agoI suppose wrapping it for other languages can be harder, but should be doable I think. The benefit of Rust though is using a much better language with rich standard library. As for toolchain, Rust can be set up basically anywhere llvm can, which is quite a lot. There are rare cases where llvm wasn't ported yet to, but I don't think they are enough to make C a compelling option in general, and I don't think any of them are gaming related. If you are in such case - then sure, but otherwise, I'd still prefer Rust.
- LucaSas 7y agoI think the Rust standard library is actually a liability in this case, not an advantage. The rust stdlib will essentially be an extra dependency and also the rust standard library doesn't have allocators, so unless you are very careful you might violate the principle of not doing allocations for the user. Also, even if it did have allocators, passing allocators from to Rust via a C interface would probably be awkward. So if you do choose to make the implementation in another language, there are extra considerations that you have to take into account.
- NohatCoder 7y agoIt always baffles me when a library comes with its own arcane build code. Instructions like "Requires Python" invariably turn out to actually mean "Requires hours of debugging".
- ginko 7y agoIMO the ideal way to get your library used is to generate a single .h and .c file.
- FreeFull 7y agoSome libraries don't even ship a .c file, all the code is in the header
- anon4242 7y agoThe downside of that is that all code is in the header. As in you will compile all the library code whenever you include the header. As third-party library code is something you probably seldom change yourself, you don't want your build to spend time building it all the time. Sure some of those libs may have some define you need to set in one of your .c files so the implementation will go there but then whenever you change that file it will result in a recompile of the third-party lib. So then you go and create a separate .c file just for the lib and we're back to why the lib author didn't do that in the first place? No, I think what sqlite does is pretty neat. If you're doing development in the sqlite code-base you have lots of files (so you can manage it) but if you just want to use sqlite, you have the amalgamation which is one .c and one .h.
- derefr 7y agoThe problem is that C is a "lingua franca" and yet has no inherent facility for compile-time codegen (i.e. "real", grammar-level macros, as opposed to C's lever-level macros), so you need something else (whether that be the venerable m4 or yacc, or random Python scripts) to serve as your substitute for "real" macros. In languages that do have "real" macros, you don't see the same problem. (On the other hand, in most of them, you see a need to call out to a C compiler to generate FFI code, which is sometimes just as bad when you're using a PL with a managed runtime and your build-env didn't otherwise have any need for a C toolchain.)
- flohofwoe 7y agoAs a warning: While C99 is a really nice language - especially over C89 - please note that the Visual Studio C compiler still does not support the full C99 specification, and it is not possible at all to compile C99 as C++ code (which sometimes is useful). I usually recommend to write library-level code in the "subset of C99 which compiles both in C and C++ mode on GCC, clang and MSVC". This is basically a version of C that's somewhere C89 and C99 (basically a "C95"). Another quite valid option is to define a pure C-API first, but implement the "inner library code" in a simple C++ (just be careful with "modern C++", since this usually results in increased compilation time and binary size).
- jstimpfle 7y agoThe one feature from C99 that I rely on (from the top of my head) is designated initializers which is still not in C++... enum { FPGAPARAM_FOO, FPGAPARAM_BAR, FPGAPARAM_BAZ, NUM_FPGAPARAM_KINDS }; struct FpgaparamInfo { const char *name; int address; int args; }; static struct FpgaparamInfo fpgaParamInfo[NUM_FPGAPARAM_KINDS] = { [FPGAPARAM_FOO] = { "FOO", 0x1337, -1 }, [FPGAPARAM_BAR] = { "BAR", 0x666, -1 }, [FPGAPARAM_BAZ] = { "BAZ", 0x42, -1 }, }; The FpgaParamInfo is basically a mapping from a FPGAPARAM_??? value to additional information. We can make as many of these mappings as we want, and can define them where we want, which means it's all nicely modular. That's not really possible when modelling in a OOP fashion. I really like this style of programming since it's data first and it minimizes the amount of actual code. Designated initializers are important because the order in which the items in "fpgaParamInfo" are given doesn't matter. Without designated initializers, programming in this style would probably lead to many hard to find bugs when the enum is changed and not all associated data items are updated.
- LucaSas 7y agoThanks for the comment, I might steal your "subset of C99 which compiles both in C and C++ mode on GCC, clang and MSVC" quote and put it in the article if that's ok ;)
- matheusmoreira 7y ago
- Keyframe 7y agoAnd then the author provides example where there’s Dear IMGUI which is c++.
- flohofwoe 7y agoDear ImGui has a very C-style API though, it's just flat a function API wrapped in a namespace, and about the only other C++ feature that's used in the API is default function arguments. For usage from C code, there's cimgui, which is an automatically generated C-API wrapper: https://github.com/cimgui/cimgui https://github.com/cimgui/cimgui
- yoz-y 7y ago> Not everyone wants to use C++ (some prefer C). This could be very well flipped over. If I have the source available, I'd much rather deal with C++ bindings.
- LucaSas 7y agoHi, I mention in the article that "It is easier in general for a C++ user to use a C library than it is for a C user to use a C++ library." which is where the advice comes from.
- yoz-y 7y agoI agree. But "wants" is a term indicating personal preference. I understand that C is best suited for maximum portability and performance. But as a C++ developer I prefer libraries that come with C++ interfaces (and you do mention that it is good to provide a C++ wrapper as well).
- LucaSas 7y agoThanks a lot for your feedback. I decided to remove that since I think you are right about indicating personal preference. I do not want this article to be a list of preferences but rather an analysis of the considerations that popular C libraries make (eg: stb, sokol, etc) and that new authors should also reason about.
- matheusmoreira 7y agoBecause the binary interfaces generated by C libraries are simple and therefore universally supported. It's just normal symbols and calling conventions. Conforming to this ABI benefits users of every other language, not just C++. C++ ABIs are rather complex and much harder to interface with. The existence of concepts like virtual functions and exceptions significantly complicates the implementation of foreign language interfaces. https://wiki.osdev.org/System_V_ABI https://wiki.osdev.org/System_V_ABI https://itanium-cxx-abi.github.io/cxx-abi/abi.html https://itanium-cxx-abi.github.io/cxx-abi/abi.html
- Anka33 7y agostruct GameObject { ... variables } GameObject_Init(GameObject &obj) GameObject_Destroy(GameObject &obj) GameObject_Update(GameObject &obj) Is just C++ with extra steps
- rehasu 7y agoThe problem for game libraries in general is that game dev lives in c++ world and c++ is awful for libraries. Most c++ devs I know would rather start writing a program by defining string than by learning to handle proper library management tooling. I'm not an expert so I'm not sure if it's just a culture thing or if there are inherent features in the language that make library usage hard, but yeah. That's that.
- overgard 7y agoIt’s pretty inherent to the tooling around the language. Basically because theres a lack of “standard” tooling, so you either go lowest-common-denominator (header only), or you have to fuss with very gross build systems that have weird quirks on different platforms. C is easier in this regard just because the language evolves so slowly that even though the build and library systems are bad, theyre a known bad that people can work around. No such luck with C++, where its bad and ever changing.
- novok 7y agoI wish the C++ world would adopt bazel eventually as their standard tooling base.
- rumanator 7y ago> I wish the C++ world would adopt bazel Why do you believe picking a specific build system is relevant wrt libraries, particularly a build system whose main selling point is build speed.
- grawprog 7y agoHonestly, for me all I would like is the ability to include/import one or more files as needed, helpfully separated by basic functionality, without needing to include the entire library, have a bare minimum of dependencies, hopefully, module independent, the ability to build painlessly for different platforms with a minimum of code changes and a painless build process for the library itself, ideally one I can include in my own build process without a lot of effort. I've used some libraries that do some of these things, but I can't think of any that does all of them.
- killmov3s 7y agoWe just need more single-file headers like nothings' stb libraries
- drjeats 7y agoNice article :) Might be worth touching on error callbacks/logging as an error handling strategy. Sometimes an error is not recoverable in the sense that the calling code can't really do anything about it, but the library should attempt to make progress anyway instead of halting the entire program. By allowing users to specify an error callback, this means they can log errors, capture stack traces, assert, or whatever. This isn't that helpful for smaller libraries with smaller-scoped processes, but if it's something like a renderer or interactive audio lib, those often just need to be given a bunch of frame time to do work with the complex input you've prepped and fed to it, and trying to propagate error codes up out of that simulation step would both contort the inner code and not be as helpful as an error callback. With an error callback you can assert, set breakpoints, or do whatever. But more importantly, by default you can have it just log so when you inevitably in a bug it doesn't prevent everyone else from getting work done while it keeps asserting until you fix your shit. This will be a less common need than the other standard error reporting mechanisms, but is important to get right if your library has these complex internal preconditions that you want to make visible to the client when violated.