Translating a C# library into Lua: closure- or metatable-based OOP, or something else?

Recently, I started thinking about a project that I would like to make with Defold and ink in a quite distant future.

However, since there are no maintained Lua implementation of the ink runtime, I thought about writing one myself.

I have little experience with Lua (but I’m not new at programming), so I don’t really know where to start to translate the official C# implementation into Lua.

I’d like to keep the Lua library as close as possible to the C# one so that it is easier to maintain, and that would mean using OOP.

So the question is, is it better to implement the objects with closures or with metatables? It seems that closures are faster, but take more memory.

I want to keep the library generic so that it works in any environment, but since I will mainly use Defold, I’d like to know what is best for it.

And if you don’t think OOP is the way to go with Lua, then how do you think I should structure the library?

(Since all of this is in a distant future, anyone wanting to write the library before me is welcome to do it!)

Thanks!

I prefer closures. It doesn’t hide behavior in metatables.

But in general I don’t think it is a very good idea to try and replicate the OO design of a library written in another language. It will probably not be easier to maintain. Use the same kind of naming and try to replicate the public API but make the underlying implementation Lua friendly.

1 Like

I was thinking it would be easier to maintain in the sense that the Lua implementation would “mirror” the C#. So when the C# implementation is updated, I would “just” report the modifications to the Lua library without to much searching what has been modified.

Could you give me an example of a “Lua-friendly implementation”?

Ink works like the following:

--- This is some kind of pseudo-code

ink = require(ink)

-- Create the story representation
-- from the JSON file created by ink.
story = ink.Story.new(json.decode(my_story_json))

-- Get the next line of the story.
if story.can_continue() then
    next_line = story.continue()
end

-- Get the list of available choices
choices = story.current_choices()

-- Select a choice.
story.choose_choice_index(1)

-- Save and load the story state.
story_state = story.state.to_json()
story.state.load_json(story_state)

So without OOP, how would it look? It seems obvious to me that the story representation has to be a table. But how would I implement the methods? In that particular case, having the story encapsulated into an object looks like an appropriate solution, at least to me.

For reference, the C# ink runtime can be found here. As you can see, it quite a big piece of code. There’s also this unmaintained Lua implementation (uses closures, I think), but I have difficulties understanding how it works exactly.

Defold-wise, at least, any approach I take will work, right?

1 Like

Did you check https://github.com/Glidias/inkhaxe ?
It’s possible to build Lua from haxe : https://haxe.org/blog/hello-lua/ and https://haxe.org/manual/target-lua.html

Ooh, thanks for the link!

I didn’t know a Haxe port existed , but I’ll have a look. How would the performance be? I tried the JavaScript example, and there is a noticeable delay that isn’t there with the JavaScript implementation written from scratch.

But if it works acceptably, I could use it as a proof of concept, before writing a Lua implementation.

1 Like

A non oop api would look like ink.can_story_continue(story_id) instead of story.can_continue(). You keep a data structure with all story data in one place and pass a reference/id to access the data.

1 Like

Sorry if I seem slow to understand, but are we talking about remove the OOP from the public API only? If that’s the case, then I don’t really see the point, except if we want to follow Lua’s conventions (which is admittedly a legitimate reason).

But if we are talking about removing the OOP from the underlying code, that would mean refactoring entirely the whole library, which seems to me a major pain and likely to introduce lots of bugs.

Sorry if the discussion drifted a bit away from Defold, and thanks for the answers. As I said, it’s not urgent, so I’ll think about it before starting this project.

What I meant with my non oop api example was to show that you could design your Lua version of the library in a way that the names of the public api functions are very similar to the api for other versions.

If you want to port line by line from another language then go for the oop version using closures or metatables.

2 Likes

Lua can use oop with metatables. I prefer lib GitHub - kikito/middleclass: Object-orientation for Lua for oop. If you what create api like in c# you should use oop.

I don’t think that you will have any problem with performance because you use classes. In syntetic tests maybe, but not in real app.

I use OOP in all my projects and it work ok.

Lua is multi paradigm language. So you can use any paradigm that fit you

1 Like

Yes, I saw that Lua is multi-paradigm. But it’s not because we can do OOP that we should. It depends on the use case and on the conventions of the language (and it’s what I was asking).

I’ll still think about it for a while before starting my project.

Ah yes, of course I can do that. I think it’s what I would do if I did something from scratch, wich is not the case here. I’ll still think about it.

Thanks for all the input!