rock 0.9.8 is out
👋 This page was last updated ~11 years ago. Just so you know.
A little less than two months after the previous release, I'm happy to announce
that the ooc compiler rock 0.9.8, codename columbia
is now out.
The impatients can readily skip to the release notes, but for those who prefer a narrative, let me tell you why I'm excited about this release.
String interpolation
We've thrown around this idea a lot since the early versions of rock since we have a few rubyists in our ranks, but only recently Alexandros Naskos took matters into his own hands and just implemented the fuck out of it.
rock now offers a syntax similar to ruby for string interpolation. Whereas previously one would have written something like:
"Player is at %s, has %.2f health and %d score" printfln(pos toString(), health, score)
One may now write:
"Player is at #{pos}, has #{health} health and #{score} score" println()
Numeric types and strings are readily supported, as for complex types, as long
as they have a toString: func -> String
method, they will work out just
fine.
Any expression might be contained within a #{}
block, not just simple
variable accesses. The ooc.vim syntax has been updated to color
those correctly. If someone wants to submit a patch to pygments
to support the new syntax, I would be most grateful.
Incidently, we now also have 'raw string literals', which makes 0-terminated
char*
instead of full-fledged ooc strings. Instead of doing this:
include stdio puts: extern func (s: CString) -> Int puts("Calling C functions.")
...where the string is an ooc String converted back to a C string, one may now directly do:
// (include + extern func here) puts(c"Calling c functions.")
...and save a few allocations / conversions.
The ::= operator
One of the things that have made my life easier in ooc was :=
operator,
also known by its friendly name declare-assign
, making what we call VDFEs
(variable declaration from expression).
Instead of having to specify a type, like so:
a: Int = 42
One may do this instead:
a := 42
And the type is inferred from the right-hand-side expression.
That worked fine for variables, but properties, even read-only ones, were always a bit of a struggle to define:
fullName: String { get { firstName + lastName } }
Now, the ::=
operator (however terrible of an idea it might be) makes it
all better:
fullName ::= firstName + lastName
Foreach with index
A minor feature perhaps, but I've found myself often doing something like that to use foreach yet have the index of the current element:
index := -1 for (elem in list) { index += 1 "list[#{index}] = #{list[index]}" println() }
Now, one may use the tuple-ish version:
for ((index, elem) in list) { "list[#{index}] = #{list[index]}" println() }
That works fine with break
, continue
, you name it.
Tuples fixes
Tuples fall into the set of features that I originally intended for one single purpose (like generics in their time) but that people keep corrupting to do their own bidding, and then I have to rewrite their implementation.
All I had in mind at first was multi-return, like this:
dup: func (a: Int) -> (Int, Int) { (a, a) } main: func { (a, b) := dup(42) // a and b are both equal to 42 }
But as it turns out, it's also convenient for destructuring, here done in a rather explicit form:
(first, last) := (list first(), list last())
Or for swapping two variables:
(a, b) = (b, a)
...which requires its own logic in the compiler (to use temporary variables where needed).
One particularly edgy case was this one:
f: func -> (Int, Int, Int) { (1, 2, 3) } g: func -> (Int, Int, Int) { f() }
Believe it or not, there was a bug in rock 0.9.7 that made g()
return (1, 1, 1)
instead of (1, 2, 3)
. I won't enter into the
details here, but hasty software architecture is to blame once
again.
Which is fine by me, btw. That's why ooc is not production-quality software, we're all just having fun. Anyway, this bug, along with a few others related to tuple assignment, are all fixed.
Cross-compiling
I've always been strongly in favor of writing multi-platform code, to the point where I would withstand the abuse of testing new versions of rock or SDK changes manually on Linux, OSX, and Windows separately.
However, when working on a game, you want to be able to iterate quickly, release new builds without having to switch computers (or even VMs) all the time.
After experimenting a bit with mingw32 on Debian, to produce Windows executable, and fighting my way through the 5-hour ordeal that is putting together a Darwin toolchain, to produce executables that run on OSX, I decided it was time to add support for cross-compiling directly into rock.
It doesn't do everything yet, for example, you still might have to adjust the
the PATH
and PKG_CONFIG_PATH
environment variables in a script before
calling rock, but it will definitely:
- Respect the
--host
command-line option, and then calli586-mingw32msvc-{gcc,ar}
as needed instead of the non-prefixed variants. - Apply
.use file
directives for the host platform (the one you are compiling for) rather than for the build platform (the one you are compiling on).
For example, with my current setup, if I want to build a Linux 64-bit binary, I can just do the usual:
rock -v -o=foo-linux64
If I want a Windows binary instead, I can do:
PKG_CONFIG_PATH=/usr/i586-mingw32msvc/lib/pkgconfig \ PATH=/usr/i586-mingw32msvc/bin:$PATH \ rock -v --host=i586-mingw32msvc -o=foo-win32.exe
If I want an OSX binary:
PKG_CONFIG_PATH=/usr/i686-apple-darwin11/usr/lib/pkgconfig \ PATH=/usr/i686-apple-darwin11/usr/bin:$PATH \ rock -v --host=i686-apple-darwin11 -o=foo-osx32
Of course in real life, I have a Makefile with variables to avoid repeating myself.
Still, with this, I can build my game for all 4 host configurations (linux32, linux64, win32, and osx32), package them, and upload them on my server with a single command, all in a few minutes, which is a sizeable improvement over my previous workflow.
Windows and threads
I've insisted for a long time on keeping the SDK cross-platform, even using the Win32 API for stuff like threads, pipes, processes, etc. And so, it was. But there was still a problem on Windows.
The version of the Boehm Garbage Collector that we used to ship had a quirk in its autotools configuration, and when building for Windows, resulted in a GC without Win32 threads support.
Consequently, running any ooc programs with threads on Windows was an experiment in futility, running into fun bugs such as 5000+ call frames of the GC calling the same function recursively, trying to find the upper limit of the stack...
By downgrading to the latest stable version, 7.2e, the GC can now be compiled with Win32 threads support, which the SDK takes advantage of, and everything is right again with the world.
Test suite
In a community, it is a miracle if more than two people can agree on much of anything, but one thing on which people were unanimous, was that ooc lacked tests. And even when tests were written, that it lacked facilities to run these tests.
However, since May 2012, thanks to Nick Markwell, for every push to GitHub, rock is being built on the Travis CI servers. At this point, we considered that rock was kind of the largest collection of tests for ooc - if rock managed to recompile itself, we were probably fine.
That proved foolish though, as many bug reports followed and showed us wrong. Eventually,
little by little, a few test cases were added. Then I added the test
command to the
sam command-line utility.
Not only can people now easily run rock's test suite on their own machines with a single command, it's also being run on Travis on every push. I still wish to make it faster, but that will have to wait for another release...
To learn about rock's test suite, you are encouraged to read the rock test README. To use sam to test your own ooc software, read sam's README.
Incremental builds
Back in 2010, a huge new feature of rock was its ability to recompile only the parts of your program that had changed. For a language like ooc, this is non-trivial, and it was very welcome.
However, it came with its share of bugs, and often one would end up with a buggy executable or simply a compilation error, and then would have to clean up temporary files and try again from scratch.
Thanks to clean-ups done in rock 0.9.5 and a recent fix just before the 0.9.8 release, I'm happy to say that things are now much better. To read about the history of this, feel free to check out the associated GitHub issue.
Bonus: teeworlds-ai
Back in 2009, I wrote a few naive bots for teeworlds in ooc. That reminds me of the days I was blissfully ignorant of the open-source community and its associated pitfalls, and I was just doing things for fun!
For old time's sake, I pulled out these legacy sources and made them compile with rock 0.9.8 again, and then I made a video for your enjoyment:
This work was particularly interesting to me because even though it retrieves player positions directly from the game, it doesn't use the game's map info. Instead, it just moves around, trying to figure out where floors, ceilings, and ledges are. That's what you see in the secondary window (drawn with GTK and Cairo). Black is unknown, green is floors, yellow is ledges.
I would love to make another AI for a game like Spelunky HD - but without the source, it might prove more challenging!
Afterword
I haven't covered all that's new in rock 0.9.8, but hopefully that's well enough to interest you in it. You can follow the instructions from the official website, or just install it using brew if you're on OSX - that's right, the latest version is on there.
Until next time, take care!
If you liked what you saw, please support my work!
Here's another article just for you:
As I've said before, I'm working on a book about lifetimes. Or maybe it's just a long series - I haven't decided the specifics yet. Like every one of my series/book things, it's long, and it starts you off way in the periphery of the subject, and takes a lot of detours to get there.
In other words - it's great if you want an adventure (which truly understanding Rust definitely is), but it's not the best if you are currently on the puzzled end of a conversation with your neighborhood lifetime enforcer, the Rust compiler.