RBXHaxe - Haxe Library for Roblox

What is Haxe?

Haxe is a language that is designed to be transpiled from. It has support for languages such as Java, C++, C#, Python, JavaScript, and of course Lua.

Why?

Well for starters, haxe is a strongly typed object orientated language. I wrote this for 3 reasons.

  1. Strongly typed languages are less error prone in my experience.
  2. Because Haxe is a language that is designed to be transpiled from, writing a game in Haxe allows you to share logic between different platforms. What this means is a carefully designed game could in theory be released on Roblox as well as another platform through a different game engine (IE Heaps).
  3. Because I could :woman_shrugging:

How?

Roblox has an endpoint where you can download its API in JSON format. You can see how this is done here. A whole lot of parsing later, it spits out valid haxe externs.

Where can one learn haxe?

Due to dev forum restrictions, I canā€™t put a direct link to that. However Haxeā€™s homepage has some great tutorials!

How do I make a roblox game in Haxe

  1. Download Haxe

  2. Learn how to write Haxe

  3. Once you have downloaded haxe and got the basics down, run haxelib install RBXHaxe

  4. Open up a place, and install nevermore (Iā€™m to lazy to write my own lazy library loader, so its got a dependency on this for now)

  5. Grab this model, put it in nevermores shared folder. This adds support for bitwise operators, and redirects Haxe unicode to the default string library.

  6. Create a Lua file in your Haxe project directory and insert the following:

_G.table = table
require = require(game.ReplicatedStorage:WaitForChild(ā€œNevermoreā€))
local globalEnv = getfenv()
local RBXEnum = globalEnv[ā€œEnumā€]
setmetatable(_G, {__index = (function(t, i) return globalEnv[i] end)})

Haxe assumes that default Lua libraries are in the global table. You canā€™t override this behavior as far as I know, so this snippet redirects them to the global scope where they reside in roblox Lua.

  1. In your build.hxml (you did actually read a haxe tutorial didnā€™t you?) insert the following compiler instructions:

-D lua_ver 5.1
-D LuaVanilla
-lib RBXHaxe
ā€“macro includeFile(ā€˜File name of the lua snippet aboveā€™)

  1. Build it, plop your files into studio and call it a day

I have questions or things arenā€™t working
I canā€™t put a discord link, so yea. Send a reply, post an issue on github, or something. I dunno.

Things to note
The Enum keyword was factored out. Enums are accessed directly.
IE

var a:SurfaceType = SurfaceType.Smooth

Operator overloading is not possible in Haxe. In order to call operators, inline functions were implemented. In other words:

var a:CFrame = new CFrame(0,0,0)
var b:CFrame = a.mul(new CFrame(1,0,0))

GITHUB LINK

Changelog
0.0.1 Initial commit
0.0.2 Fixed a bug where Int64s were not being imported correctly
1.0.0 Functions that return an instance now return a Dynamic instead to avoid having to cast constantly
1.0.1 Fixed a bug where ProtectedStrings were not a defined type (damn documentation not listing them as a basic type reeee)
2.0.1 Implemented Instance.newInstance in order to create an instance without casting. (also accidentally messed up the version number :man_shrugging:
2.0.2 Fixed a bug where function arguments that were named ā€œfunctionā€ would cause a type error.
2.0.3 Imported function type into all class files
2.0.4 Implemented Game:GetService() (oops)
2.0.5 Changed the keyword game to an instance type
2.0.6 Made instance functions that return an instance (FindFirstChild, WaitForChild, etcā€¦) return a dynamic to reduce casting
2.0.7 Made game methods non-static (oops)
2.0.8 Added in the debug class
3.1.2 O-boy, am I behind on changelogs. A LARGE amount of fixes and stuff including stuff such as changing int64 definitions to ints, fixing a bug where arguments called ā€œoverrideā€ would die horribly, and a ton of other tweaks.
3.1.3 Added new types. Updated API to current version.

31 Likes

May I ask: are you releasing the Lua transpiler or are you advertising one that already exists? Or is RBXHaze a library that streamlines interaction between Nevermore and the Haxe transpiler? Just to disclose: I havenā€™t installed Haxe yet.

Haxe by itself compiles to many languages (JS, Flash, C++, Python, C#, PHP, Java, Lua) - this project just adds bindings for Roblox Lua in the Lua transpilation mode.

2 Likes

Nevermore is only used for the lazy library loader included with it. Basically requires are called by name rather than tree path like they are in roblox, and I needed something to handle that behavior.

It does a very minor change to the actual transpiled file through that metatable snippet, but other than that, the transpiler is pre-existing.

1 Like

Oh wow. Canā€™t believe you actually released something.
Iā€™ve been waiting for this tool ever since you showed it off on Twitter.
Iā€™ll definitely be using this in future projects!
:+1:

How does this benefit students who are learning the codes such as C++ in university?

Iā€™ve heard the Lua output code from Haxe could use some work. Probably wouldnā€™t use this for performance intensive code, otherwise this is really cool.

1 Like

I wouldnā€™t even use the outputted code period. Itā€™s the worst compiled code Iā€™ve seen, beating out even MoonScript.

The one Hello World script I made is 366 lines long. A hello world program. Hereā€™s the link if you care to see.

1 Like

A large amount of the compiled code is never really executed unless you use certain language features.

The only code that really executes at run time in your example is

local Fruits = _hx_tab_array({[0]=ā€œAppleā€, ā€œPearā€, ā€œGrapeā€, ā€œBananaā€ }, 4);
haxe.Log.trace(Fruits,_hx_o({fields={fileName=true,lineNumber=true,className=true,methodName=true},fileName=ā€œMain.hxā€,lineNumber=6,className=ā€œMainā€,methodName=ā€œmainā€}));
local _g = 0;
while (_g < Fruits.length) do
local Fruit = Fruits[_g];
_g = _g + 1;
haxe.Log.trace(Fruit,_hx_o({fields={fileName=true,lineNumber=true,className=true,methodName=true},fileName=ā€œMain.hxā€,lineNumber=9,className=ā€œMainā€,methodName=ā€œmainā€}));
end;

Thatā€™s still way too much for Hello World.

1 Like

Thatā€™s not a hello world :shrug:

An actual hello world would be one line executing, and then the setup for different data structures and watnot (which would never be called)

Although for iterating through a small array and printing it, yea its a bit much

For reference, I wrote a stress test for your example in haxe and pure lua.

Lua:

local fruits = {"apple", "pear", "grape", "banana"}

local startTime = tick()

for i=0, 10000 do
	for k=1, #fruits do
		print(fruits[k])
	end
end

print(tick() - startTime)

Haxe:

class Main {
	static function main() {
		var fruits:Array<String> = ["apple", "pear", "grape", "banana"];
		var startTime:Float = untyped __lua__("tick()");
		for(i in 0...10000)
		{
			for(fruit in fruits) {
				trace(fruit);
			}
		}
		var endTime:Float = untyped __lua__("tick()");
		trace(endTime-startTime);
	}
}

Both running in studio

Haxe runtime: 23.477569103241

Lua runtime: 19.081376075745

So your haxe is 18.72% slower than Lua. A decent difference, but still workable :man_shrugging:

You could increase haxe performance by using the correct type (lua table is a thing in haxe), but to stay true to your source, I kept it as an array.

We are transpiling here so of course there is gonna be a lot of boilerplate code which is basically what you are seeing.

Have you tried comparing a Hello World program to something a little longer? I bet the difference in lines isnā€™t nearly as drastic. Iā€™m sure it could be a lot shorter if you want to sacrifice readability, but then weā€™d be back at square one with complaints about that despite Haxeā€™s intention if you not needing to touch the outputted language. Its a careful balancing act.

1 Like

Iā€™ve been trying out this project and whenever I try to reference a script object, it fails to compile because it says protected string is an unknown type. Any solutions?

Yeah, I already ran into this bug. It was fixed in 1.0.1.

Just run

haxelib update RBXHaxe

and it will fix itself.

So when creating a new instance through new Instance(ā€œPartā€) or whatever, it still needs a cast to the actual type. Would it be possible for you to create a fix for this?

Yeah, I have just been casting it thus far. I will go write something up to fix it.

You can now use

Instance.newInstance(ā€œPartā€)

in order to create an instance without casting.

run haxelib update in order to install the changes.

So now that the bit library has been ported to roblox, are you planning on porting it over to this instead of using nevermore for it?

Trying to access runservice throws me a syntax error. What am I doing wrong?