List items
Items from the current list are shown below.
Newpipe
14 Mar 2025 : Day 5 #
Yesterday we got Thigg's sailing-the-flood-to-java Java code compiling into a native library for Sailfish OS. I can't claim any credit for this because Thigg did all of the hard work. It's nevertheless an important step towards getting a similar library built from the NewPipe Extractor code.
But we can't just replace the Java portion of sailing-the-flood-to-java with the NewPipe Extractor code and expect it to work, because Thigg also had to do some work to expose the interface in the Game.java implementation. The really interesting bit happens in two files: the GameInterface.java file and the Shared.java file.
We don't have to worry about the internals of GameInterface.java, but it does help to consider the interface, which looks like this (with apologies for abusing Java syntax):
A complex type, all bundled up into a simple string.
The method returns a byte array, which is also a very low level datatype, but which is interesting because Thigg has implemented this using FlatBuffers. The method returns the full game state, including the colour of each block on the game board. Since there can be a large number of blocks (e.g. a 256 by 256 board would be an array of 65536 values), this would make for a rather cumbersome JSON structure. FlatBuffers provide a more efficient interop mechanism, which is why Thigg chose it.
For NewPipe my guess is that we won't need to worry about performance because we'll mostly be passing relatively short strings backwards and forwards (e.g. URLs). If we were transfering multimedia (images, videos or audio) then JSON would be a terrible, terrible, terrible choice. But for short strings, it's going to be perfectly efficient (probably no less efficient than FlatBuffers in practice).
Nevertheless it's interesting to see the mechanisms for how Thigg achieved this. You can see the Flatbuffers calls in flatbuffers.sh, which looks like this:
After passing this file through the flatc tool these two times, we end up with the following files:
On the C++ side we need to look in the jgateway.cpp file to see what's going on. When the postMessage() method is called with a method name provided as a string, the Java code is called using JGateway__invoke(). This returns a byte array stored in the result parameter which can then be deserialised as follows:
So what we see is that when we pass parameters from C++ to JavaScript it's done as a JSON string. The data that's returned from Java to C++ is transferred as a FlatBuffer.
The good news is that we don't have to worry about the FlatBuffer part as we can pass our results back in JSON format too. We'll need to make some changes to the JGateway C++ code and to the Shared Java code. But actually the changes will likely be pretty minimal.
Tomorrow I'm going to have another go at getting the GraalVM build to work on the Sailfish SDK. It might work, or it might not. But it feels like it's worth a try.
But we can't just replace the Java portion of sailing-the-flood-to-java with the NewPipe Extractor code and expect it to work, because Thigg also had to do some work to expose the interface in the Game.java implementation. The really interesting bit happens in two files: the GameInterface.java file and the Shared.java file.
We don't have to worry about the internals of GameInterface.java, but it does help to consider the interface, which looks like this (with apologies for abusing Java syntax):
public class GameInterface { @SneakyThrows public static byte[] startGame(String json); private static byte[] getGameState(); @SneakyThrows public static byte[] flood(String json); }There are three methods. The first initialises the game. The input string is a blob of JSON containing a size entry and a numColors entry, for example like this:
{ "size": 20, "numColors": 4 }The code inside this method takes this JSON and populates an instance of the NewGameParams class:
public class NewGameParams { public int size; public int numColors; }JSON is being used as the interop mechanism because it can be managed as just a string and we can move strings between C++ and Java without too much concern for types. On the other hand, the JSON has its own type semantics which can be harnessed to share richer datatypes between the C++ and Java code.
A complex type, all bundled up into a simple string.
The method returns a byte array, which is also a very low level datatype, but which is interesting because Thigg has implemented this using FlatBuffers. The method returns the full game state, including the colour of each block on the game board. Since there can be a large number of blocks (e.g. a 256 by 256 board would be an array of 65536 values), this would make for a rather cumbersome JSON structure. FlatBuffers provide a more efficient interop mechanism, which is why Thigg chose it.
For NewPipe my guess is that we won't need to worry about performance because we'll mostly be passing relatively short strings backwards and forwards (e.g. URLs). If we were transfering multimedia (images, videos or audio) then JSON would be a terrible, terrible, terrible choice. But for short strings, it's going to be perfectly efficient (probably no less efficient than FlatBuffers in practice).
Nevertheless it's interesting to see the mechanisms for how Thigg achieved this. You can see the Flatbuffers calls in flatbuffers.sh, which looks like this:
flatc --cpp -o ../qt-part/src/ src/main/resources/gamestate.fbs --gen-object-api flatc --java -o ./src/main/java/ src/main/resources/gamestate.fbs --gen-object-apiThe first call generates the files needed for the C++ code. The second call generates the files needed for the Java code. The input comes from gamestates.gbs which looks like this:
namespace Game.model; table GameState { field:[short]; won: bool; steps: [int]; max_steps:int; width: int; height: int; scores: [Score]; } table Score { player: int; steps: int; won: bool; combo_score: int; } root_type GameState;This tells us that there are two datatypes that will be used in both the C++ and Java code and which may need to be transferred between the two. The GameState is what's returned by startGame().
After passing this file through the flatc tool these two times, we end up with the following files:
- GameState.java: Java source file containing the GamePlay interface.
- GameStateT.java: Java source file containing the GamePlayT table interface.
- gamestate_generated.h: C++ source containing the GamePlay and GamePlayT interfaces.
On the C++ side we need to look in the jgateway.cpp file to see what's going on. When the postMessage() method is called with a method name provided as a string, the Java code is called using JGateway__invoke(). This returns a byte array stored in the result parameter which can then be deserialised as follows:
auto gamestate = Game::model::UnPackGameState(result);The UnPackGameState() method comes from the generated code which returns a GameStateT instance which is then used to populate the QGameState object that's a member of JGateway.
So what we see is that when we pass parameters from C++ to JavaScript it's done as a JSON string. The data that's returned from Java to C++ is transferred as a FlatBuffer.
The good news is that we don't have to worry about the FlatBuffer part as we can pass our results back in JSON format too. We'll need to make some changes to the JGateway C++ code and to the Shared Java code. But actually the changes will likely be pretty minimal.
Tomorrow I'm going to have another go at getting the GraalVM build to work on the Sailfish SDK. It might work, or it might not. But it feels like it's worth a try.
Comments
Uncover Fediverse comments