Making a Game in Rust
michaelfairley.com
michaelfairley.com
> First class code hot-loading support would be a huge boon for game developers. The majority of game code is not particularly amenable to automated testing, and lots of iteration is done by playing the game itself to observe changes. I’ve got something hacked up with dylib reloading, but it requires plenty of per-project boilerplate and some additional shenanigans to disable it in production builds.
Lua is a great fit here and interops with Rust(and just about everything else) very well.
As an alternative, though, there are several Rust-based scripting languages that attempt to expose some of the power of the type system, etc., while being more amenable to dynamic loading, REPL, etc:
Also the JS runtime is huge and the build system is complex. You can run Lua in ~400kb or less and LuaJIT tends to walk over any other jitted interpreted runtime.
It really is too bad that JavaScript is different from Lua. Lua predates it by 2 years. [1]
Criticizing a language on syntax is something I would hope we could move past. Mr Newman didn't read about the history and purpose of Lua, otherwise he would know it was targeted at scripting data loads for simulations written in Fortran. Hence the 1 based indexing. Lua syntax is one of its SELLING points.
++ mutates a variable in place, has non-trivial pre/post behaviour (many junior devs don't understand it), and its brevity causes it to be used inline, which results in complex one liners.
String concatenation using '+' is not considered to be a great feature in dynamically typed languages. When you can't guarantee the types of the operands, it can lead to unexpected results. Pass in two numbers, and it this returns the result of the sum. Pass in two strings, and they're concatenated.
'continue' is a strange one. I just don't use it, and I'm not sure why anyone thinks it adds anything. It's like an early return, in that it can cause some difficulty in code comprehension.
So it feels a little like Lua is being criticised for making some good calls.
goto continue
and then a ::continue::
slapped immediately before the relevant loop's end. Surprisingly, it's not affected by the rule forbidding "going into new variable scope" - because "the scope of any variable ends before 'end', wink wink, nice trick we left here for you no?"Yes, one of the things Perl got very right, and yet it is still brought up by people as evidence of what makes Perl look like line noise (along with == vs eq, which is the same thing, even if the problem is less troublesome in that case).
> So it feels a little like Lua is being criticised for making some good calls.
All too often languages (and aspects of them) are criticized mistanely as being worse when all that is presented is how they are different. All too often we vilify the unusual just for being unusual.
I admit I haven't bothered to research the current state of things, but how do more recent Lua bindings handle this? Does Lua 5.3 actually have a proper solution here, or do most bindings just wrap every single call with lua_pcall? I didn't do that in my bindings because I wanted to offer the full speed of Lua, but it's certainly an option.
Because Lua supports coroutines with a stack independent from the system C (Rust) stack, you often want to be careful mixing your stack-allocated objects. Lua 5.2 added lua_callk which allows yielding and resuming coroutines across the C API (that is, "yielding" a coroutine with a nested C or Rust function invocation).
Leveraging Lua's awesome coroutine support is one of the biggest reasons to use Lua, IMO.
Also, Lua can safely recover from out-of-memory (OOM) scenarios. On OOM it will throw an error that can be safely caught. Any Lua API which might internally allocate memory can throw this error, not just the lua_call family of routines. Quality libraries are expected to also handle OOM, which usually can be accomplished the same way as handling any other error: wrapping and anchoring temporaries, directly or indirectly, in the Lua VM.
Generally I've found that any time you're interacting with an FFI in Rust all bets are off and you need to be very aware of what your libraries do and what their runtime looks like(just in C/C++).
I was able to build the Rust version fast, and the SDL library is actually quite usable/stable for most things.
Flappy-Rust has particle effects, the beginnings of parallax-scrolling and basic collision detection. The rust code ended up being pretty reasonable however I'm quite sure there are a few places I could have simplified the sharing of assets.
If anyone is interested in this space check out my repo: https://github.com/deckarep/flappy-rust
Also please see the README.md where I talk a bit more in-depth about how the Rust version differs from the Go version.
Here is a .gif preview of the first iteration of the game: https://github.com/deckarep/flappy-rust/blob/master/flappy-r...
When I teach kids javascript I start with an etch-a-sketch. It's aided by a simple library to hide the mechanics of the HTML canvas element, context, etc. This allows it to be small enough that they can view it all in one go and build upon it.
print("Draw with the arrow keys");
var cx=320;
var cy=240;
function update() {
// the arrow keys have key codes 37,38,39 and 40
if (keyIsDown(38)) { cy-=1; }
if (keyIsDown(40)) { cy+=1; }
if (keyIsDown(37)) { cx-=1; }
if (keyIsDown(39)) { cx+=1; }
fillCircle(cx,cy,6);
}
run(update);
There might be merit in writing one of these in every language (and a companion that uses the mouse), maybe placing it on github . With a really simple program like this you can focus on learning the language while making something. It's a tough job figuring out how to learn a language while simultaneoulsy learning how to write the boilerplate needed to get something onscreen.Otherwise rust-sdl2 is batteries included, there may be more ceremony than html5 canvas but it's easy to hide behind a simple interface. It has many simple examples.
But, like with C++, as long as the language doesn't come with a standard graphics library, I doubt the "official" books can really use graphics apps as a learning tool.
The MiniFB code does seem to be a good starting point for this sort of thing.
I absolutely advocate using graphics and games as the hook to keep people interested in learning. [1] "The immediacy of many 8-bit computers was awesome. Instant boot to a graphics-ready command line and program editor. I was instantly hooked". And that's why, despite the really bad fit, I support the addition of a 2d std library to C++, and would do likewise for Go, Rust, etc.
For C/C++/etc devs looking for something similar, BFD objcopy supports an "-I binary". It will emit an object file with _binary_objfile_start, _binary_objfile_end and _binary_objfile_size symbols.
But I have got to say that making it a language feature means that Rust is truly a batteries included language.
Having first-class language support for resource files looks fantastic.
Isn't that exactly what resource files give you or am I confusing something? Anything you add in resource files have are accessible as a static variable.
E.g. bytes [0, 12, 99] would become "\x00\x0C\x63".
Almost every language has facilities to treat a string as a set of bytes, so this works basically everywhere. It's nice because you don't need any special language-level support for it.
let stuff = include_bytes!("my.file");I had never heard of xxd until your comment though, so thank you very much!
The problem with objcopy outside of unconvenient usage and naming is that naive objcopy will result in your binary having executable stack [1]. You can change a symbol name, but that's also unconvenient.
Check resulting binary with:
$ readelf -lW the_binary | grep GNU_STACK
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x8
$
Notice: RWE instead RW.Also: https://wiki.gentoo.org/wiki/Hardened/GNU_stack_quickstart#H...
[0] https://github.com/graphitemaster/incbin
[1] https://news.ycombinator.com/item?id=10816322#10818085
EDIT:
My shell script - bin2o.sh:
#!/bin/sh
set -e
filename="$1"
name=$(echo "$1" | sed "s/[^A-Za-z0-9]/_/g")
obj="$2"
echo \
" .section .rodata
.global ${name}
.type ${name}, @object
.global ${name}_size
${name}:
.incbin \"${filename}\"
1:
${name}_size:
.int 1b - ${name}
.section .note.GNU-stack,\"\",%progbits
" | gcc -x assembler -c - -o "$obj"EDIT: And for those interested, you might want to check out Rust's recent inclusion in a AAA title: https://www.reddit.com/r/rust/comments/69s225/rust_makes_it_... :P
[0] http://daringfireball.net/2010/04/iphone_agreement_bans_flas...
[1] https://developer.apple.com/app-store/review/guidelines/
This internal Rust discussion focuses on it for more details: https://github.com/rust-lang/rust/issues/35968
This gist is that Apple is requiring bitcode, but isn't giving easy access to the LLVM version they use for their own tools. This means there's not a path forward to support bitcode generation from Rust that would align with Apple's requirements. This currently only effects iOS on Watch and TV, meaning you can easily target macOS and iPhone without issue, but I fear that the writing is on the wall.
These are exactly the kind of archaic requirements that would be a nonstarter or otherwise kill market support for a store given any actual competition.
Instead, they are able to leverage it to try and push their 'approved' languages and developer environments - furthering anticompetitive lock-in.
Apple making money from selling devices (or even distributing "at a loss" devices which they benefit from having exist so they can better act as software vendors) and them making money from their store are two separate revenue streams, after all.
I wouldn't be surprised if that's true for Apple for some of its offerings.
No, "products from brand A" is never a category. However, a company may create a brand new market with a product, and they may be the only company with a product in that market for a while. But that still doesn't mean the category is "products by that company", it just means it's whatever new category was created from the product.
For example, the iPhone arguably created a new category of smartphones. But competitors quickly introduced their own products in this same category (e.g. Android).
To your comment about bitcode being stable, I don't think that bitcode stability and forward compatibility was something guarateed until 4.0.
% cat test.rs
fn main() {
println!("Hello, world!");
}
% rustc --emit llvm-bc -C lto test.rs
% LIB=$HOME/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib
% xcrun --toolchain XcodeDefault clang -o test test.bc ~/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/liballoc_jemalloc-ea49ffd3fee5264c.rlib
warning: overriding the module target triple with x86_64-apple-macosx10.12.0 [-Woverride-module]
1 warning generated.
% ./test
Hello, world!
% rustc --version
rustc 1.18.0-nightly (91ae22a01 2017-04-05)
% xcrun --toolchain XcodeDefault clang --version
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
That's using Apple's compiler to compile bitcode from rustc. The .bc file has significant chunks of Rust's libstd embedded, so the test isn't as trivial as it seems. To embed bitcode as they want for the App Store, you can use -fembed-bitcode, except it doesn't work because liballoc_jemalloc wasn't compiled with that option (but that's trivial to fix).I also tried compiling for iOS, which seems to work, but I didn't bother to test the resulting binary.
(As for stability, according to the announcement[1], the previous policy was that bitcode would be readable "up to and including the next major release", which was already reasonable from the perspective of keeping a third-party compiler's output compatible.)
[1] http://blog.llvm.org/2016/12/llvms-new-versioning-scheme.htm...
I wonder if the dependency on jemalloc can go away once custom allocators stabilize. Then the macOS system allocator could be used.
Incidentally, I just tried updating my nightly Rust, and it stopped working - clang started failing to read the bitcode, bailing out with a vague "error: Invalid record". This is not too surprising, because Rust just landed a big LLVM upgrade two weeks ago. Newer LLVM can read older bitcode files but not the other way around, and even though LLVM 4.0 was released months ago, Apple seems to only sync with trunk yearly, along with major Xcode releases. (You can tell based on the --version output.)
However, Rust can be built against an external LLVM (rather than the fork it uses by default), and AFAIK it tries to preserve compatibility with older versions. So it should still be possible to use the latest rustc, you just have to compile it yourself against a slightly older LLVM.
edit: corrected thinko about .rlibs
The Reddit comments are great.
Edit - Could you tell me why you downvoted me. This wasn't even the slightest bit argumentative, so I am not sure who I angered.
The game wasn't written in rust, is was used in a texture and drawn on a wall.
That said, if you're dynamically interpreting code at runtime that was downloaded from the internet, you're only allowed to do that using JavaScriptKit. You cannot, for example, embed the Lua interpreter and then interpret code downloaded (but code in your app bundle is fine).
This sounds (sort of) encouraging. I was kind of expecting to learn it by putting in an hour here and there e.g. 2-4h/month. But I'm beginning to think that might not cut it...
Rust ... isn't. I love Rust, but you do need to put some dedicated time into learning it. We're hoping to improve this!
I'm not a rust dev (definitely interested in picking it up though), but heavily into clojure and learning haskell slowly on the side. I hear this complaint a lot from people who think that because they learnt C#, Go, Java and Swift in a weekend that all languages will be just as quick to stick in their heads.
Also, I mean, sure, the language may not have new things, but ultimately the goal is to use the damn thing, not learn new concepts :) Go is amazing for that.
I totally agree that expecting to learn a random language in a weekend is unreasonable.
Python is actually a good case for this, where choices to make the language simpler and more uniform led to knock-on effects that ultimately lead to aspects of it being quite cumbersome (IMO, obviously). For example, how anonymous functions are specified, how cumbersome and limited that makes map and grep, and how that leads to list comprehensions, which for all but the simplest cases are much more complicated to read and write than a simple chain of maps and greps (assuming something better than lambda).
At the other end of the spectrum you have APL and its relatives. Lots of work to learn and become proficient in, but then you can read and write terse, performant and versatile programs with it quickly. Of course, the pool of people that can understand what you wrote is orders of magnitude smaller than with some of the easier to learn languages.
I understand the urge to make Rust as accessible as possible, and I applaud that effort. I just hope that Rust continues to walk the line of accessibility without compromise on the core ideals.
But lots of things can be made easier to learn via better tooling, better docs, or better diagnostics. Or just removing papercuts.
One such thing I've recently been thinking about is https://github.com/Manishearth/rust-clippy/issues/1737 , which would be cool if it got big.
Rust, being largely of new things (in implementation and and presentation, if not theory), has plenty of room currently to explore changes and features that have little or no negative aspects. I'm a little worried about what happens when the low hanging fruit is fine though.
That said, rust has been very good so far at assessing the merit of proposed changes. I recall a few months back that reasoning was explained, and it assessed positive and negative aspects on a few well defined axes.
The five platforms support is bloody impressive.
I have not tried out Rust yet, but couldn't this be solved by wrapping the float in a single-field struct that checks for NaN on construction, implements Ord using PartialOrd and otherwise passes everything through to the ordinary float inside?
If this isn't possible, I'm definitely interested in the reasons.
[1] https://github.com/rust-lang/rfcs/blob/master/text/1210-impl...
Wait, are +-Inf considered unordered? Edit: Nope. inf == inf, so you should only have to worry about NaN. https://is.gd/Xi5jdr
It seems like the right way is to add min/max fns to the Iterator trait for PartialOrd that ignore items where partial_ord returns None.
And do you want max(NaN, 0) to be NaN or 0? There is a case to be made for either possibility.
Then again according to float semantics, sum of a list containing NaN must be NaN, so it would make sense to extend that principle to min/max.
But the naive implementation of min/max will never output NaN because NaN always compares false. (... unless NaN is the first element. ugly.)
Maybe making it explicit would be best: Make an explicit fn for float collections that filters out NaN in an Iterator, then claim that Item is totally ordered. I'm too much of a rust noob to know if this is possible. (playground here I come!)
https://hackernoon.com/compiling-rust-to-webassembly-guide-4...
first point - insecure
Would you rather have a safe that thousands of thieves try to break on a daily basis, or would you prefer that only casual thieves try to break it every once in a while?
I would trust my money to the safe that has been thoroughly tested. Checking for safety is pretty much all the flash team does these days.
inefficient
Its actually as or more efficient than JavaScript. Where there lots of crap flash ads and programs? Sure, but that is because Adobe made it easy to create flash apps.
I am writing a 3D engine in Flash that displays and updates millions of polygons at 60 frames per second. I can't do that in JavaScript yet.
To the end user? It doesn't matter one bit.
To your peers? It's interesting and educational to talk shop.
Also it's an alternative to C++ which is heavily used in gamedev. I personally believe Rust can shine in gamedev and every experiment is interesting.