Skip to content

libretro.h: add a link interface for machines joined by a cable - #19454

Draft
XenuIsWatching wants to merge 1 commit into
libretro:masterfrom
XenuIsWatching:link-interface
Draft

libretro.h: add a link interface for machines joined by a cable#19454
XenuIsWatching wants to merge 1 commit into
libretro:masterfrom
XenuIsWatching:link-interface

Conversation

@XenuIsWatching

Copy link
Copy Markdown

A frontend that runs several cores at once has no way to let two of them reach each other. Each instance is generally loaded from its own copy of the shared library, so each has its own copy of every global and no in-core coordinator can be shared between them. The frontend is the only thing they have in common, so the frontend has to host the bus.

That is enough to emulate the cable a great many machines shipped with: a Game Boy serial cable, a Game Boy Advance link cable, a GameCube-to-GBA cable, a PlayStation link cable. Today a core that wants this either does without, or emulates both machines itself inside one instance, which only works when both ends are the same core.

This is not what RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE does. That joins separate frontend instances over a network and carries packets in real time, and it says so by disabling pause, rewind, fast forward and save state loading while a session is up. This joins core instances inside one process and carries payloads stamped on emulated time, with the frontend deciding how far each core may advance so that a transfer lands on the cycle it is due. The timing is the feature rather than an implementation detail: a link protocol delivered a millisecond late is a link protocol that has failed, which is why it cannot be built on a packet interface with no clock.

The frontend never interprets a payload. All knowledge of the wire protocol stays in the cores, which is what lets two DIFFERENT cores emulating two different machines share one cable, and what keeps unrelated cores apart: peers whose protocol_id strings differ are never connected to each other.

The grant returned by advance() is required to be a pure function of the participants' published ticks, with no wall-clock input and no timeout. A frontend that violates that desyncs any peer replaying the same inputs and expecting the same result, which is what rollback and lockstep multiplayer both do.

The port handle follows the two frontend-hosted resources this header already carries. attach() returns an opaque retro_link_port_t * and every other call takes it, the way retro_vfs_open_t returns a retro_vfs_file_handle * and retro_open_mic_t a retro_microphone_t *. It names a PORT rather than a core, which is why it is needed at all: a machine with more than one socket, and a GameCube has four, has to say which one it means on every call.

A frontend that does not implement this returns false, and a core that receives false behaves exactly as a machine with nothing in its socket, which such a core must already do. Cores and frontends can therefore adopt this independently of one another.

A frontend that runs several cores at once has no way to let two of them
reach each other. Each instance is generally loaded from its own copy of
the shared library, so each has its own copy of every global and no
in-core coordinator can be shared between them. The frontend is the only
thing they have in common, so the frontend has to host the bus.

That is enough to emulate the cable a great many machines shipped with:
a Game Boy serial cable, a Game Boy Advance link cable, a
GameCube-to-GBA cable, a PlayStation link cable. Today a core that wants
this either does without, or emulates both machines itself inside one
instance, which only works when both ends are the same core.

This is not what RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE does. That
joins separate frontend instances over a network and carries packets in
real time, and it says so by disabling pause, rewind, fast forward and
save state loading while a session is up. This joins core instances
inside one process and carries payloads stamped on emulated time, with
the frontend deciding how far each core may advance so that a transfer
lands on the cycle it is due. The timing is the feature rather than an
implementation detail: a link protocol delivered a millisecond late is a
link protocol that has failed, which is why it cannot be built on a
packet interface with no clock.

The frontend never interprets a payload. All knowledge of the wire
protocol stays in the cores, which is what lets two DIFFERENT cores
emulating two different machines share one cable, and what keeps
unrelated cores apart: peers whose protocol_id strings differ are never
connected to each other.

The grant returned by advance() is required to be a pure function of the
participants' published ticks, with no wall-clock input and no timeout.
A frontend that violates that desyncs any peer replaying the same inputs
and expecting the same result, which is what rollback and lockstep
multiplayer both do.

The port handle follows the two frontend-hosted resources this header
already carries. attach() returns an opaque retro_link_port_t * and every
other call takes it, the way retro_vfs_open_t returns a
retro_vfs_file_handle * and retro_open_mic_t a retro_microphone_t *. It
names a PORT rather than a core, which is why it is needed at all: a
machine with more than one socket, and a GameCube has four, has to say
which one it means on every call.

A frontend that does not implement this returns false, and a core that
receives false behaves exactly as a machine with nothing in its socket,
which such a core must already do. Cores and frontends can therefore
adopt this independently of one another.

Definitions only; no behaviour changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant