The new hid option enables users to map a specific Libretro device (or multiple devices) to the input ports. For instance, this allows users to map a Multitap controller with the snes9x core.
In the current version of the application, we have strictly hardcoded the captured runtime application (FFI Libretro frontend) as well as the streaming transport (WebRTC). This commit makes it possible to choose these components at runtime.
In this commit, we no longer manage initially connected users separately from the rooms, and instead, we treat all users as abstract app sessions, rather than hardcoded WebRTC connections. These sessions may contain all the transport specifics, such as WebRTC and so on.
Rooms, instead of having the hardcoded emulator app and WebRTC media encoders, now have these components decoupled. In theory, it is possible to add new transports (e.g., WebTransport) and streaming apps (e.g., wrapped into an ffmpeg desktop app).
- Fixed broken image cache for the first stage RGBA frames. It was not a thread-safe one, which led to image tearing (parts of old images in multiple consecutive frames).
- 180 flip function for the OpenGL coordinate system has been moved into the rotation part.
- Optimized YUV converter.
- Optimized color converters:
- Use __restrict pointers.
- Draw image pixels with the faster bitwise operators and pointer arithmetic as 32/16bit LE numbers (may break on ARM devices like RPi).
- Pass uints for less num conversions.
- Much faster XRGB -> RGBA conversion with Go's stdlib 32bit flip.
- Wrapped RGBA images into a custom struct in order to bypass opacity tests for the standard Go png functions, which needed for the PNG file export. Before that we set RGBx opacity byte explicitly during the pixel format conversions (much slower).
- Made Libretro core shutdown more deterministic. When we run a C core separately from the main Go process we have to make sure that the C core is not doing anything in its syscall while we stopping the emulator. Basically, a blocking call may be suspended on the Go's side while the other goroutines have no knowledge of that.
- Less info level logs.
- Added recording user label.
- Enabled RTCP sender reports by default, which may help with A/V sync.
- Check onMessage webrtc handler if it's set. May crash the program if not.
- Fixed some make dirs permissions.
- Enabled console colors (since MS has finally fixed their bloody Terminal).
- Disable log in some tests.
- Updated deps.
This PR contains refactored code.
**Changelog**
- Added new net code (the communication architecture was left intact).
- All network client IDs now have custom type `network.Uid` backed by github.com/rs/xid lib.
```
The string representation of a UUID takes 32 bytes, and the new type will take just 16.
Because of Golang JSON serialization problems with omitting zero-length empty slices (it can't)
and the need to use UID values as map keys (maps don't support slices as keys),
IDs are stored as strings (for now).
```
- A whole new WebSocket client/server implementation was added, as well as a new communication layer with synchronous and async call handlers.
- WebSocket connections now support dedicated Ping/Pong frames as opposed to original ping text messages.
- Used Gorilla WebSocket library doesn't allow concurrent (simultaneous) reads and writes, so this part was handled via send channel synchronization.
- New API structures can be found in the `pkg/api` folder.
- New communication protocol can be found in the `pkg/com/*` folder.
- Updated communication protocol is based on JSON-encoded messaging through WebSocket and has the following structure:
```
Packet
[id] string — a globally unique identification tag for the packet to track it trough a chain of requests.
t uint8 — contains packet type information (i.e. INIT_PACKET, SDP_OFFER_PACKET, ...).
[p] any — contains packet data (any type).
Each packet is a text message in JSON-serialized form (WebSocket control frames obviously not).
```
```
The main principle of this protocol and the duplex data exchange is:
the one who initializes connection is called a client, and
the one who is being connected to is called a server.
With the current architecture, the coordinator is the server, the user browsers and workers are the clients.
____ ____
↓ ↑ ↑ ↓
browser ⟶ coordinator ⟵ worker
(c) (s) (c)
One of the most crucial performance vise parts of these interactions is that
all the server-initiated calls to clients should be asynchronous!
```
- In order to track synchronous calls (packets) with an asynchronous protocol, such as WebSocket, each packet may have an `id` that should be copied in all subsequent requests/responses.
- The old `sessionID` param was replaced by `id` that should be stored inside the `p` (payload) part of the packet.
- It is possible to skip the default ping check for all connected workers on every user connection and just pick the first available with the new roundRobin param in the coordinator config file `coordinator.roundRobin: true/false`.
- Added a dedicated package for the system API (pkg/api/*).
- Added structured logging system (zerolog) for better logging and cloud services integration.
- Added a visual representation of the network message exchange in logs:
```
...
01:00:01.1078 3f98 INF w → c Handshake ws://localhost:8000/wso
01:00:01.1138 994 INF c ← w Handshake localhost:8000
01:00:01.1148 994 INF c ← w Connect cid=cep.hrg
01:00:01.1158 994 DBG c connection id has been changed to cepl7obdrc3jv66kp2ug cid=cep.hrg
01:00:01.1158 3f98 INF w → c Connect cid=cep.2ug
01:00:01.1158 994 INF c New worker / addr: localhost, ...
01:00:01.1158 3f98 INF w Connected to the coordinator localhost:8000 cid=cep.2ug
01:00:02.5834 994 INF c ← u Handshake localhost:8000
01:00:02.6175 994 INF c ← u Connect cid=cep.hs0
01:00:02.6209 994 INF c Search available workers cid=cep.hs0
01:00:02.6214 994 INF c Found next free worker cid=cep.hs0
01:00:02.6220 994 INF c → u InitSession cid=cep.hs0
01:00:02.6527 994 INF c ← u WebrtcInit cid=cep.hs0
01:00:02.6527 994 INF c → w ᵇWebrtcInit cid=cep.hrg
01:00:02.6537 3f98 INF w ← c WebrtcInit cid=cep.2ug
01:00:02.6537 3f98 INF w WebRTC start cid=cep.2ug
...
```
- Replaced a monstrous Prometheus metrics lib.
- Removed spflag dependency.
- Added new `version` config file param/constant for compatibility reasons.
- Bump the minimum required version for Go to 1.18 due to use of generics.
- Opus encoder now is cached and the default config is 96Kbps, complexity 5 (was 196Kbps, 8).
- Changed the default x264 quality parameters to `crf 23 / superfast / baseline` instead of `crf 17 / veryfast / main`.
- Added a separate WebRTC logging config param `webrtc.logLevel`.
- Worker now allocates much less memory.
- Optimized and fixed RGB to YUV converter.
- `--v=5` logging cmd flag was removed and replaced with the `debug` config parameter.
**Breaking changes (migration to v3)**
- Coordinator server API changes, see web/js/api/api.js.
- Coordinator client event API changes:
- c `GAME_PLAYER_IDX_CHANGE` (string) -> `GAME_PLAYER_IDX` (number)
- c `GAME_PLAYER_IDX` -> `GAME_PLAYER_IDX_SET`
- c `MEDIA_STREAM_INITIALIZED` -> `WEBRTC_NEW_CONNECTION`
- c `MEDIA_STREAM_SDP_AVAILABLE` -> `WEBRTC_SDP_OFFER`
- c `MEDIA_STREAM_CANDIDATE_ADD` -> `WEBRTC_ICE_CANDIDATE_RECEIVED`
- c `MEDIA_STREAM_CANDIDATE_FLUSH` -> `WEBRTC_ICE_CANDIDATES_FLUSH`
- x `MEDIA_STREAM_READY` -> **removed**
- c `CONNECTION_READY` -> `WEBRTC_CONNECTION_READY`
- c `CONNECTION_CLOSED` -> `WEBRTC_CONNECTION_CLOSED`
- c `GET_SERVER_LIST` -> `WORKER_LIST_FETCHED`
- x `KEY_STATE_UPDATED` -> **removed**
- n `WEBRTC_ICE_CANDIDATE_FOUND`
- n `WEBRTC_SDP_ANSWER`
- n `MESSAGE`
- `rtcp` module renamed to `webrtc`.
- Controller state equals Libretro controller state (changed order of bits), see: web/js/input/input.js.
- Added new `coordintaor.selector` config param that changes the selection algorithm for workers. By default it will select any free worker. Set this param to `ping` for the old behavior.
- Changed the name of the `webrtc.iceServers.url` config param to `webrtc.iceServers.urls`.
(experimental feature)
Before a worker can start, it should have a configuration file. In case if such a file is not found it may request configuration from the coordinator to which it connected.
Added example logic if a worker is needed to be blocked until a successful packet exchange with a coordinator is being made.
* Add error return for config loader
* Add config loaded flag to worker
* Add zone flag
* Add a custom mutex lock with timout
* Refactor worker runtime
* Refactor internal api
* Extract monitoring server config
* Extract worker HTTP(S) server
* Add generic sub-server interface
* Add internal coordinator API
* Add internal routes and handlers to worker
* Add internal worker API
* Refactor worker run
* Migrate serverId call to new API
* Add packet handler to cws
* Extract handlers for internal worker routes in coordinator
* Pass worker to the worker internal heandlers
* Cleanup worker handlers in coordinator
* Add closeRoom packet handler to the API
* Add GetRoom packet handler to the API
* Add RegisterRoom packet handler to the API
* Add IceCandidate packet handler to the API (internal and browser)
* Add Heartbeat packet handler to the API (internal and browser)
* Rename worker routes init function
* Extract worker/coordinator internal ws handlers
* Update timed locker
* Allow sequential timed locks
* Add config request from workers
* Add nil check for the route registration functions
* Add initial external configuration files support.
These external configuration files allow changing app params at the runtime without recompilation.
* Find config files with specified directory in the tests
* Add aspect ratio recalculation config
* Clean code
* Add new configuration files into the Docker container image
* Add shared core and config paths into the Libretro cores config
* Split ROM <-> Emulator mapping between workers and coordinators
* Extract coordinator config
* Add shared worker/coordinator server config
* Add explicit embedded shared worker/coordinator struct for auto-config reflection fill
* Remove default stun/turn servers from the config
* Extract and add new ice servers config structures
* Update coordinator config params
* Add auto emulation lib loader based on the runtime OS/arch
* Update configuration structures
* Remove shared config embedding
* Add missing network config params
* Add game library external config
* Remove unused config parameters
* Add WebRTC encoder external options
* Add user dir for config search
* Update config loader
* Update config
* Add generic downloader with Grab lib implementation
* Add a simple file downloader backed by the grab lib
* Add initial Libretro core repos abstractions
* Expose compression info for Libretro cores repository records
* Add pipe-based abstract file downloader
* Refactor downloader
* Refactor Libretro repos
* Add worker coresync stubs
* Add multiprocess-safe HTTP-based core manager implementation
* Remove Libretro cores from the repo
* Keep custom N64 cores in te repo for now
* Add Libretro cores repo select in the config
* Fix http manager repo switch
* Cleanup code
* Add greedy Libretro lib loader
* Don't crash when arch map is not set
* Disable dynamic recompiler for pcsx core by default since it's could cause a crash
* Use global Libretro dynalib handler
* Shorten the default Libretro cores store path
* Update zip extractor implementation
* Remove explicit fig lib field markings
* Add config note to the README file
* Add GitHub repo backend for the core downloader
* Fix GitHub repo param list in the manager factory
* Add env variables reader with CLOUD_GAME prefix
* Re-optimize ice server info struct custom marshaler