Recently, as a member of OpenFL’s leadership team, I decided that I wanted to do more to keep the community up-to-date with what new features I’m working on for the next versions of OpenFL and Lime. In today’s post, I’m going to talk a bit about the upcoming implementation of the openfl.display.NativeWindow
class, which is based on a core API introduced way back in Adobe AIR 1.0. The purpose of the NativeWindow
class is to open a new window (with its own stage and display list) on desktop operating systems, like Windows, macOS, and Linux.
If you’re not familiar, OpenFL is an implementation of the APIs available in Adobe Flash Player (and Adobe AIR) using the Haxe programming language. Projects built with Haxe can be cross-compiled to JavaScript, C++, and many other targets — making it available on the web, desktop apps, mobile apps, and more. No browser plugins required, and fully native C++ apps.
Implementing NativeWindow
Lime, which provides OpenFL’s integration with the native operating system, has the ability to open multiple windows through its use of SDL. SDL is a C++ library that provides a cross-platform interface for things like graphics, sound, gamepads/joysticks, and more. It’s commonly used in games, but isn’t necessarily restricted to that category of program.
Lime’s Window
class was actually already exposed in OpenFL, including creating a new stage for each window. However, one of my goals as a contributor is to ensure that developers adopting OpenFL can easily migrate from Flash or AIR. So I needed to wrap Lime’s API with a new class that offers the familiar interface of AIR’s NativeWindow
. In the process, I discovered that I had to expose a few new APIs that Lime didn’t provide yet. And, I was able to find some ways to optimize and improve cleanup of a window and its stage (and the entire application… more on this below).
For the most part, implementing NativeWindow
was pretty straightforward because SDL provides many similar APIs to what is available in AIR, and many of these were already exposed through Lime. However, not all of these SDL APIs were fully exposed yet, and there were important differences in behavior between Lime’s Window
and AIR’s NativeWindow
where Lime needed some tweaks to support both behaviors. For instance, by default, Lime’s Window
immediately opened on creation, but AIR keeps a window hidden until the visible
property is set to true
, or the activate()
method is called.
Lime had actually exposed a way to hide a window by default, but there wasn’t yet a property/method to reveal it later. So I needed to dive into Lime’s C++ code and expose a new window visible
property to Haxe that could be changed at runtime. Similarly, AIR allows developers to optionally set minimum and maximum dimensions of a NativeWindow
, and Lime hadn’t yet exposed SDL’s functions for that feature yet.
Bridging Haxe and C++ in Lime
Adding new C++ code to Lime that can be called from Haxe involves changes in several files.
- The .hx class that provides the public API. In the case of the new
visible
property, that’s Window.hx. - Lime APIs are usually powered by a separate “backend” class for each target. In this case, Window.hx calls into NativeWindow.hx (to be clear, that’s
lime._internal.backend.NativeWindow
, which is different fromopenfl.display.NativeWindow
). - C++ backend classes call methods defined in the NativeCFFI.hx class.
NativeCFFI
tells Haxe how to find the C++ APIs exposed by the compiled .ndll binary (or .hdll binary for HashLink). It requires separate declarations for Neko, HashLink, and hxcpp, so it’s kind of a confusing file to work with because there’s a lot of duplication. - The ExternalInterface.cpp file, which exposes the public C++ API for the .ndll and .hdll binaries. This file usually doesn’t contain the concrete implementation, but instead, is more of the glue that talks to other C++ classes that do the actual work.
- The .h and .cpp file where the actual C++ concrete implementation exists. In this case, that’s SDLWindow.h and SDLWindow.cpp.
Adding new Haxe APIs to Lime that are implemented in C++ isn’t actually very hard, but it is (admittedly) a complicated process because there are so many places that can be affected by a single API change.
Improved disposal/cleanup
As I was implementing OpenFL’s NativeWindow
class, I took some time to consider memory management. Many OpenFL apps have one window only, including mobile and JS/web apps (yes, Lime and OpenFL still have a “window” concept when building for web browsers). With that in mind, there’s often only one Stage
and display list too. However, with NativeWindow
, each additional window creates a new Stage. When closing a window, it’s important to clean up that stage as well, to make sure that there aren’t any memory leaks, or worse, memory leaks plus extra code running without end — even if it isn’t needed anymore. (The first thing I checked was that Event.ENTER_FRAME
wasn’t still firing out of control, but thankfully, that was already working properly.)
Ideally, when an OpenFL app exits, it should close all of its windows and remove any references to itself from static variables. Doing this manually isn’t necessary on desktop and mobile because the operating system will clean everything up automatically when the app exits. However, when targeting JS/web, if you were to manually “exit” an OpenFL application (such as by calling Lime’s System.exit()
method), or if you were to manually call close()
on the “window”, that doesn’t necessarily mean the browser has navigated somewhere else. The app could be embedded in a page with other content, and it may be important to be able to load a new instance of the same OpenFL app again, or even replace it with a separate OpenFL app (or something entirely different), inside the same parent HTML DOM element at a later time.
The easiest improvement was setting the static properties lime.app.Application.current
and openfl.desktop.NativeApplication.nativeApplication
both to null
on exit. It wouldn’t be possible for the application instance to be garbage collected without this change. Similarly, a number of event listeners defined by the application are added to static dispatchers that expose notifications from C++. These should be removed when the application exits too, so that they don’t prevent garbage collection.
Gamepad.onConnect.remove(__onGamepadConnect);
Joystick.onConnect.remove(__onJoystickConnect);
Touch.onCancel.remove(onTouchCancel);
Touch.onStart.remove(onTouchStart);
Touch.onMove.remove(onTouchMove);
Touch.onEnd.remove(onTouchEnd);
On the JS/HTML side, “closing” the “window” should also clean up the HTML DOM. Lime creates a <canvas>
element that either renders to WebGL or falls back to software rendering. It also adds some event listeners to the canvas for mouse and touch. When the window is closed, or the application exits, the canvas should be removed from the DOM, and any listeners added to DOM elements should be removed too. Again, we need things to be garbage collected, and everything returned as close to the original state as possible.
var element = parent.element;
if (element != null)
{
if (canvas != null)
{
if (element != cast canvas)
{
element.removeChild(canvas);
}
canvas = null;
}
}
After all of these changes, Lime and OpenFL feel like much better HTML/JavaScript citizens, and it opens up some new possibilities for more complex websites where OpenFL is one small part of a larger system.
Where can I find the code?
If you want to try out the new Everything in this post has been officially released to Haxelib in Lime 8.1 and OpenFL 9.3!NativeWindow
feature, or the improved application/window/stage disposal, you’ll need to check out both Lime’s 8.1.0-Dev branch and OpenFL’s 9.3.0-Dev branch on Github, or download both the lime-haxelib artifact from a successful Github Actions Lime 8.1.0-Dev nightly build and the openfl-haxelib artifact from a successful Github Actions OpenFL 9.3.0-Dev nightly build. Of course, this code is not yet ready for release to Haxelib, so use at your own risk in production. There may still be some bugs.
If you love these devlogs, and my contributions to OpenFL and Lime in general, please consider a monthly donation as a token of appreciation on my GitHub Sponsors page. Thank you!