Core & UI
In order to create a viewer project, you need to establish a pathway between
the user's UI, and the server the virtual world is running on.
One of the core design decisions about the benthic project was that the UI
should be completely independent from the business logic of the project, and
it should be possible for developers to easily swap out UI frameworks
without much difficulty. Doing it this way has created three discrete layers
in the benthic project.
User Interface
The front end, which users can interact with. All this does is receive user input to send to the core, and display output it receives from the core. All interaction between the UI and the core should be done with messages as small as possible, to ensure that the least amount of logic is dependent on non-portable UI systems.Core
The asyncronous runtime logic of the program. This handles all of the communication between the UI and the server, the parsing and manipulating of server data, and sending messages to the UI to display. The majority of the project lives here.Server
The server that is running the upstream metaverse server code. This is the server that manages user connections, responds to requests from clients, and informs clients about state changes.
The first thing that starts running is always the UI. The UI is the entry
point to the viewer, and triggers the Core to begin accepting messages. In
the debug UI, that is found in the
main()
function in the main.rs file.
The Bevy example UI contains a
plugin, which can be implemented by other bevy projects. This plugin is
responsible for message handling between the core and the UI, along with
receiving and rendering mesh data, and can be used by other projects by
adding the plugin
in the main function. The main project is expected to handle viewer state,
and events raised by the plugin.
This plugin triggers the
start_core()
and
start_listener()
functions on startup.
There are now three independent async processes running after startup. The call order looks like this.
- start_core(), which spawns an async task where the core will run until the UI is shut down
- initialize(), which instantiates the core's actix mailbox, and spawns an async task for listening to UI messages that runs until the core is shut down.
- listen_for_ui_messages() which listens on the port specified at the UI's startup, and notifies the now-running mailbox for events from the UI.
- Listen_for_core_events() which listens on the port specified at the UI's startup, and notifies the UI for events from the core.
UDP Sockets
The UI and Core communicate through local UDP sockets, which send byte data
back and forth between the two components. Messages sent between the UI and
core are defined in
messages/ui, and are seperated into
UIMessage
and
UIResponse
objects.
UIMessages
are for messages coming from the core to the UI, and
UIResponses
are for messages coming from the UI to the core. These types are
serialized
into JSON bytes bytes by serde, and
deserialized
in the same way. This allows the decoding to be a straightforward operation
of parsing JSON bytes, which can be done easily in any language with
existing libraries, allowing any UI framework to use the main rust core.