Moonshine

How to pass callbacks out of Lua

You may want your Lua code to respond to actions or events that occur on your page. The good news is that this is easy with Moonshine, in fact a Lua function created inside a Moonshine VM has a similar interface to a JavaScript function.

Let's put a button on a page and the add a click listener in the Lua.

<html>
	<head>
		<script src="./js/moonshine.js"></script>
		<script src="./js/passing-callbacks.js"></script>
	</head>
	<body>
		<button>Click me!</button>
	</body>
</html>
// Forward print() messages to the console
shine.stdout.write = function () {
	console.log.apply(console, arguments);
}

// Create API function
function addButtonListener (callback) {
	// Get reference to the button
	var button = document.getElementByTagName('button')[0];

	// On click, execute callback
	button.addEventListener('click', function () {
		callback.call();
	}, false);
}

// Create a VM and pass in our function
var vm = new shine.VM({
	addButtonListener: addButtonListener
});

vm.load('./lua/callback.lua.json');
-- Create listener function
function handleButtonClick ()
	print 'The button was clicked!'
end

-- Pass callback to the host function
addButtonListener(handleButtonClick)

The callback is passed to the host function as if it were any other function in Lua. Once in the host environment, the Lua function can not be called directly, but it does have an API similar to that of a JavaScript function. A Lua function object is executed in JavaScript using foo.call(context, arg1, arg2, ...) or foo.apply(context, argArray).

Note that in each case the context is ignored; it is only present to conform to JavaScript's function object API. In fact, the conext is optional in a call to foo.apply().

Retaining and releasing Lua callback functions

In the above example, the handleButtonClick() function is in the global scope of the Lua environment, this means that it's always available. However, if the callback was a local and that local goes out of scope, we will run into some problems.

If a variable goes out of scope and Moonshine is unaware of any references to it, the variable may be garbage collected. To prevent this from happening, you'll need to advise the VM that you are retaining a reference to the function. You do this by calling the retain() method on the Lua function object.

⋮
function addButtonListener (callback) {
	// Retain reference to the callback
	callback.retain();

	// Get reference to the button
	var button = document.getElementByTagName('button')[0];

	var callbackWrapper = function () {
		callback.call();

		// Only allow one click
		button.removeEventListener('click', callbackWrapper, false);

		// Release reference to the callback
		callback.release();
	};

	// On click, execute callback
	button.addEventListener('click', callbackWrapper, false);
}
⋮