Virtual topology
In previous examples, we looked at what the topology is and how to work with it. The CubiOS operating system provides information about the relative positions of modules and their screens, and this is what we call “topology”. However, working with real, that is, physical topology is not always convenient. Quite often tasks arise that require, for example, preliminary disordering of the topology to begin the game process. For example, if the goal of the application is to solve a cube, then the cube must initially be disordered.
The process of disordering a cube to a state that can subsequently be guaranteed to be completely ordered is what we call scrambling.
In order to de-order the cube without changing the physical arrangement of the modules, a virtual topology is used. In essence, a virtual topology is a copy of the real topology made at a certain point in time. This copy is stored in application memory and allows you to perform face twists without actually interacting with the cube. In other words, virtual topology makes it possible to take the original topology of a cube and randomize it in a controlled way.
To simplify working with virtual topology, the Cubios::Scramble class is used (see documentation).
This class provides a number of functions that allow you to apply single or multiple twists to a virtual topology. In addition, this class provides the ability to receive callbacks called before and after applying the twist.
Generalized steps
So what steps do you need to take to get it working?
Step 1: Determine what information will be stored in the virtual topology.
The fact is, a virtual topology can be a repository of not only actual information about module and screen numbers, but also any other data associated with the topology. These could be, for example, resource identifiers or game level fragments or something else.
Step 2: Create a class inherited from Cubios::Scramble to implement callback handlers
Step 3: Populate the virtual topology with the original data, for example, make a copy of the current physical topology.
Step 4: Perform the required number of twists to the virtual topology.
Step 5: Use the result.
The example
So, let's look at an example that uses a virtual topology to scramble an initially assembled cube by tapping on one of its faces.
The first thing to do is to describe our virtual topology and what exactly we will store in it. For this we will use an array
of structures
As you can see, the array contains information about 8 modules (MODULES_MAX) of 3 screens (SCREENS_MAX) each. Moreover, in addition to the standard Cubios::TOPOLOGY_place_t values, we will additionally store the resource identifier and the corresponding rotation angle. This is needed to draw a cube map.
The next step will be to write a class inherited from Cubios::Scramble, whose task will be to apply transformations (twists) to our custom virtual topology, as well as process events.
Let's take a look at this class.
The header file looks like this:
Everything is more or less clear here: there are three overridden callback functions and two methods for accessing virtual topology data. But in addition, there are 2 private variables mapping and mappingTemp, both of type mapping_t.
It is clear that one of them is needed to store the virtual topology, but why the second?
The answer is quite simple: two copies are needed because we need to know the state of the virtual topology both before and after applying the twist. In other words, we need to know “how it was” and “how it is now" !
Let's look at the source code below:
-
on_BeforeTwistcallback is called before a transformation (a twist) is applied to the virtual torology. This means that if we want to remember the previous state of the topology, we just need to make a copy of the current state. That's what we do. -
After the twist is performed, the
on_MappingChangedcallback is called first. This callback makes it possible to find out exactly how the modules and screens were rearranged after applying the twist. And this means that in the body of this callback we must implement data exchange between the cells of our virtual topology array. -
The last callback
on_Twistis called at the very end and signals the completion of scrambling of the virtual topology. In this example, we simply output a message to the log, but in real applications this callback can be used differently.
Now let's move on to the main application.
First we declare the necessary variables:
-
scramble is our virtual topology handling class
-
mapping is an instnace of virtual topology array the application uses for rendering
ScrambleApp::ScrambleApp():angles{0,90,180,270} { }
void ScrambleApp::InitializeResources() { for (uint8_t module = 0; module < MODULES_MAX; ++module) { for (uint8_t screen = 0; screen < SCREENS_MAX; ++screen) { Cubios::TOPOLOGY_place_t place; TOPOLOGY_getPlace(module,screen,Cubios::TOPOLOGY_orientation_mode_t::ORIENTATION_MODE_MENU, &this->mapping.value[module][screen].place); this->mapping.value[module][screen].place.face = Cubios::TOPOLOGY_getPlaceOrientation(this->mapping.value[module][screen].place.face, this->mapping.value[module][screen].place.position);
ScramblingCPPWrapped for easier reading. Turn wrap off to inspect exact line lengths.}
How does it all work?
Well, in this function we do the following: we take the current physical topology and orientation of the cube by iterating through all modules and screens, save this topology in the mapping variable, simultaneously supplementing it with custom information about resource identifiers and angles, and at the end we create the objects we need.
In this way, we initialize our virtual mapping with an initial state identical to the state of the physical cube.
Next, we can proceed directly to rendering.
As you can see, the rendering process consists of simply selecting the desired resource and the corresponding rotation angle from the virtual topology data array.
The next stage is to make sure that when you tap the cube, the virtual topology will scramble.
Let's look at the on_Tap function:
-
First of all, we must pass the current state of our virtual topology to the
scrambleobject. -
Next, we, in fact, can apply twists the required number of times - five times in this case.
-
And in the end, we get the transformed virtual topology from the
scrambleobject back into the application.
But that's not all.
We have one small but important point left - how to ensure synchronization of virtual torology changes between modules? After all, it is absolutely obvious that mapping a virtual topology onto a real one will work correctly only in the case when all cube modules operate with identical data, right? And if suddenly this data “diverges,” then we will immediately get visual rendering artifacts.
There are many ways to ensure data synchronization between modules - some more optimal, others less so. But all of them, one way or another, come down to the idea of transferring data from the “master” module (or modules) to the “slave” modules. That is, the master module is responsible for generating data, and the slave modules are only consumers of this data, but do not participate in the generation.
This approach is also used in our application.
The calculation of virtual topology transformations is carried out on the 0th module, which constantly sends data to neighboring modules:
That is, as soon as the virtual topology on the 0th module has changed, all neighbors will immediately know about it.
And thus we will get an application that renders a cubemap and scrambles it when we tap the cube.
Good job!