Taylor's Five Year Anniversary
Intro
This is going to be a long rambling post about the past five years of Taylor. Be prepared for it not to be well edited and for my thoughts to jump around a bit. I thought about making this a video but life has not been kind recently so this is what I have the energy for. So please sit back and enjoy my ravings.
At a Glance
- Five Years ago Today Where it all started
- Major Refactor Mistakes What I did wrong with the refactor
- Taylor Squash Changes to how Taylor concatenates your project into one file
- Exporting Options to overhaul exporting
- Dependency Management Thoughts on how to use gems with Taylor
- Future Plans What's to come
- Outro Cheers 🍻
Five Years ago Today
commit 0d9def80e734eb3df48469d4a4a4a8ebb964f42c
Author: Sean Earle <sean.r.earle@gmail.com>
Date: Fri Apr 9 22:02:11 2021 +0930
â–Ž
â–Ž Initial commit
â–Ž
â–Ž Got basic mruby integration but terrible structure and not very DRY
This was the first ever commit to the Taylor repository so I treat it as the birthday for Taylor. I figure I’d cover some of my gripes, lessons learnt, and future plans.
As a fun fact, this is the list of implemented Ruby methods in the very first commit:
init_window
set_target_fps
window_should_close?
begin_drawing
end_drawing
draw_fps
clear_background
draw_rectangle
get_frame_time
Colour.new
Colour#red
Colour#red=
Colour#green
Colour#green=
Colour#blue
Colour#blue=
Colour#alpha
Colour#alpha=
Major Refactor Mistakes
The major refactor took about 2.5 years and the way I went about it was (in my opinion) all wrong. Sadly I only realised that about 2 years into the process!
After working on Taylor for a while, I finally decided to give all the methods a refactor to be my vision of what Taylor should be like to use, instead of just being a one to one copy of Raylib. This meant that it was going to be a much more “Ruby like” experience with classes and keyword arguments. This was also raised as the issue Top-level namespace is overloaded.
I did sit down and write out a version of the example.rb you see on the front
page to try and figure out how I wanted this to look. After a little bit of
back and forth with a trusted friend I had made up my mind about how I wanted to
structure everything.
The mistake came when I decided to just start rewriting all the C++ code into the new shape. I realised I could have taken a much shorter path of adding a layer of Ruby on top to obfuscate the lower C++ code and slowly chip away at rewriting the C++ code later. This would have let me get the release out a lot earlier and have people using it and able to give me feedback.
In terms of code, my proposed refactor would have initially just looked like this:
class Window
def self.open(width: 800, height: 480, title: "Taylor Game")
init_window(width, height, title)
end
end
Other than that though, I’m happy with how it all turned out. I think the new design is significantly better and helped me find quite a few nasty bugs hiding in the C++ layer thanks to my usage of pointers and my understanding of memory management.
Taylor Squash
I wrote about this in the first Taylor Monthly but to
summarise. Currently when you run taylor squash what it does is concatenate
all the ruby files in the order they’re required. This can lead to a variables
scope being a lot larger than in development.
I’d like to replace it with something a lot smarter that uses lambdas to keep the scopes from polluting each other. It still won’t be perfect but I think it’ll be good enough to not affect most games and applications. I also believe it could be useful for “compiling” standard Ruby applications for easy distribution.
If you know anything about me it’s that I think way too much about how to distribute the things I develop. It’s one of the reasons I love web development so much and also a major reason Taylor exists. I could rant about this all day but will instead leave it there.
You can see a prototype of my proposed squash redesign in your browser.
Exporting
Right now if you run taylor export it’ll actually run a bunch of Docker
commands. If you run taylor export --dry-run in a standard Taylor project
you’ll see these commands.
docker run --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien,target=/app/game --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien/exports,target=/app/game/exports --env USER_ID=$(id -u ${USER}) --env GROUP_ID=$(id -g ${USER}) hellrok/taylor:linux-v0.4.1
docker run --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien,target=/app/game --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien/exports,target=/app/game/exports --env USER_ID=$(id -u ${USER}) --env GROUP_ID=$(id -g ${USER}) hellrok/taylor:windows-v0.4.1
docker run --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien,target=/app/game --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien/exports,target=/app/game/exports --env USER_ID=$(id -u ${USER}) --env GROUP_ID=$(id -g ${USER}) --env EXPORT=osx/intel hellrok/taylor:osx-v0.4.1
docker run --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien,target=/app/game --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien/exports,target=/app/game/exports --env USER_ID=$(id -u ${USER}) --env GROUP_ID=$(id -g ${USER}) --env EXPORT=osx/apple hellrok/taylor:osx-v0.4.1
docker run --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien,target=/app/game --mount type=bind,source=/home/sean/Syncthing/code/taylor-org/examples/jumpy_alien/exports,target=/app/game/exports --env USER_ID=$(id -u ${USER}) --env GROUP_ID=$(id -g ${USER}) hellrok/taylor:web-v0.4.1
As you can see that’s 5 Docker images that are very kindly hosted over on Docker Hub. In total this is about 2~GB of images per version of Taylor. This will only get larger the more platforms I support as each platform is one Docker image (more or less). I’m very skeptical of how long they’ll let me keep hosting this and transferring this much data without charging me or cutting me off.
I also don’t love that it means every developer using Taylor needs to have Docker installed and correctly configured so they can export their games. I think on modern ARM based Macs this has a fairly large performance penalty because all the Docker images are for AMD64 based CPUs. I also haven’t tried this on Windows in a long time, but I’m hoping it still works there.
The OSX Docker image is based on OSXCross and I’m so thankful this project exists or I wouldn’t support OSX at all. It is a bit of an extra maintenance headache though as the setup for OSXCross is a bit of a pain.
The story of the Android Docker image is quite similar but even dodgier as I’m
pretty sure I’m not supposed to accept the EULA for ndk. I just don’t want
users to have to do anything other than run taylor export when they want to
export their games.
I straight up don’t support iOS because there’s no way to compile an app for it from Linux.
The theme here is walled gardens are extremely annoying for me as a developer who wants to create an easy developer experience for cross-platform deployments.
I’m sure you can tell from the above paragraphs that I don’t love this setup. I’m strongly considering refactoring the whole thing to be much simpler. The main reason for all this complexity is because when you export your game, you’re actually compiling Taylor with your game’s code instead of the Taylor CLI tooling which actually is just a Taylor application.
I’ve got a few alternatives in mind but I’m really not sure what I prefer or what I’ll seek out.
Zig
One option is to use Zig as my C++ compiler instead of GCC/Clang/OSXCross/emscripten. This is apparently able to cross compile but I’m skeptical it’ll be as magic as it would need to be to handle building Taylor. I suspect it’d get me about 80% of the way there but then require heaps of fiddly nonsense to get the last 20% and even then, it’d still require Docker (probably).
Alternatively, you could probably use mrbc and Zig to compile a library Taylor could
dynamically load. I think this would be much more likely.
TinyCC
I recently remembered TinyCC and think it could be used inside Taylor via
libtcc. This is very interesting but I suspect it’s not the most supported
compiler and I will run into a lot of issues supporting it myself. Also I think
it might not target the platforms I need like WASM or OSX.
As above it’d still require mrbc to generate game.h before compiling it.
mrbc
You’ve probably seen me mention this a bunch during this blog post already. This
is the CLI tool that comes with MRuby that takes your Ruby file and turns it
into bytecode for MRuby. Taylor uses this to convert your squashed game into a
game.h file to compile into the engine. mrbc also supports outputting a
game.mrb file that is basically the same as the game.h but can be loaded via
require 'game' by MRuby.
Because this is built with MRuby, I believe I could include it into Taylor
itself so we can keep the single binary, drop Docker, and still let you export
games. The big problems I have with this approach is that your game is no longer
just a single binary and your assets. You now have to ship it with the
game.mrb file. I worry this will lead to much easier reverse engineering of
games similar to what happens with Godot or Unity.
This also doesn’t solve the issues of having to prepare the assets for emscripten for the web export. There are absolutely workarounds I could implement but I worry they would feel second rate.
This would greatly simplify what taylor export needs to do on your local
machine. Instead of needing to compile the entirety of Taylor all it would need
to do would be to zip up the different platforms with your game.mrb. I believe
this is similar to how Godot handles it’s exporting with pck files.
Run a Build Server
This one is quite interesting because it alleviates a lot of setup for end users. It’s super simple for new developers. Sadly, it’d cost me money, be a lot of development, and would surely be attacked as a source of free CPU/RAM.
I also think people shouldn’t just trust me to not inject evil things into their
executable. Obviously I would build this in a way that people could self host
their own build servers but that’s a lot more work. The nice thing is it’d be
built to leverage the Docker images used by taylor export so there’s still
that option.
I just have very little interest in hardening a server to the point that malicious actors can’t give me a bad day and stop people from building their games.
Dependency Management
This is something I’ve spent a lot of time thinking about but have not put any energy into getting working yet. I think it’ll be the big thing that sets Taylor apart from other MRuby based game engines like DragonRuby.
SIDE NOTE: DragonRuby is a super cool project you should check out. It’s got a lot of support and a large community. It may work better for you than Taylor depending on how you like to code. I’ve chatted with Amir in the past and he’s a nice person. He’s also ported DragonRuby to cool platforms like the Nintendo Switch.
I’ve had a few thoughts about how to handle this, so I’ll lay them out below.
Build My Own Package Manager
Building my own package manager sounds like a fun project and that entices me a lot. Obviously this would be a huge amount of work and I genuinely don’t think I’d do a great job given my lack of experience in this field. Especially when there are so many options out there already doing an excellent job.
So I could just…
Use Bundler
Using bundler is an incredibly enticing option as it’s already made, has all the infrastructure, and everyone already knows how to use it. The tricky part would be figuring out what path to include into Taylor and how to wire it into the Docker image.
This is the option I’ve considered the most and the one I think would probably be the most likely to be built. The nice thing is I could build this in isolation to test it without having to modify Taylor at all. So it would be a fun side project.
Although I could just have…
User Defined MRuby Configurations
MRuby actually has it’s own package management in the way of gems defined in the build configuration. One of the main reasons I want to support this is because Taylor actually has a great base for building distributable Ruby applications that aren’t games. Things like simple CLI tools or tiny web servers. I think it would add a lot to how useful Taylor is if you could compile it without Raylib attached. This would make the binaries a lot smaller (I think) but would require a fair bit of work to make happen.
This has the issue that I’d have to distribute MRuby as a separate file alongside Taylor because it would have to be a dynamically loaded library. I’d also have to figure out how to let people compile their own MRuby configurations easily and that sounds very hard.
Especially since people can clone Taylor, edit the taylor.gembox file, and compile it themselves. They would have to build the Docker images too but that’s also not very hard, just time consuming.
I do think this could be worth pursuing as it would allow developers to write performance critical code in C/C++/Rust/etc and easily integrate it into Taylor. I don’t believe this is a functionality any other MRuby game engine provides. I know Godot does via their GDNative extensions though!
Future Plans
I’ve got plans for the future of Taylor, so here they are in the order I expect to do them.
Make Games
This is the big one! I started Taylor five years ago because I wanted to make games with Ruby and I didn’t like any of the options I found at the time. So I made the mistake of building my own game engine thinking I would be making games with it in no time!
Here we are five years later and I’ve actually produced a few small games using Taylor as I’ve been building it, but I want to build something bigger to really put Taylor through its paces and find its pain points.
v1.0.0
Once I’ve built a decent sized game I will consider that proof that Taylor is finally ready for its v1.0.0 release. The release of v0.4.0 I consider a bit of a technical preview for v1.0.0. What this means is you shouldn’t expect any major changes to what’s there now, at most there will be new additions. I’m doing my best to not introduce any breaking changes.
If I do they are documented under migrations as I feel that is essential for a serious project that respects the developers using it. I may have issues with the leadership of Rails but their upgrade process and documentation is actually amazing and the level I aspire to. When I look at picking up a major framework or library migration documentation is something I specifically check for.
3D
Yes, the third dimension! I’ve been asked about this a few times and I do plan to support 3D as Raylib will make this pretty easy. I’m not planning on doing any work for it until I’ve gotten Taylor to v1.0.0 though. I’m mostly delaying this because it will be a lot of work that will be quite mundane. I also have zero experience with 3D and worry about doing it wrong and not realising. I already feel this way about my shader implementation as I’ve never used it because I don’t know how to write shaders.
And More
There’s plenty more I’d like to do, as alluded to above I definitely want to rework some internal systems and make developers lives easier. There’s also virtual reality support in Raylib and that could be cool to implement even if I don’t own a VR headset.
Honestly, I’m not sure where the future will take Taylor but I’m excited to find out.
Outro
That’s about all I can think to ramble about right now, I’m sure I’ll find more down the line and I’ll be sure to post about them when I do!
I am amazed at how far I have taken Taylor and am loving working on and with Taylor! I don’t see myself stopping work on Taylor any time soon as I am having too much fun and have too many plans.
Thank you for reading and thank you for being interested in my little project I started all those years ago just sitting on my couch with my secondhand Lenovo Helix Ultrabook and a couple beers.
Cheers 🍻