mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 10:35:44 +00:00
202 lines
4.6 KiB
Go
202 lines
4.6 KiB
Go
// Package api defines the general API for both coordinator and worker applications.
|
|
//
|
|
// Each API call (request and response) is a JSON-encoded "packet" of the following structure:
|
|
//
|
|
// id - (optional) a globally unique packet id;
|
|
// t - (required) one of the predefined unique packet types;
|
|
// p - (optional) packet payload with arbitrary data.
|
|
//
|
|
// The basic idea behind this API is that the packets differentiate by their predefined types
|
|
// with which it is possible to unwrap the payload into distinct request/response data structures.
|
|
// And the id field is used for tracking packets through a chain of different network points (apps, devices),
|
|
// for example, passing a packet from a browser forward to a worker and back through a coordinator.
|
|
//
|
|
// Example:
|
|
//
|
|
// {"t":4,"p":{"ice":[{"urls":"stun:stun.l.google.com:19302"}],"games":["Sushi The Cat"],"wid":"cfv68irdrc3ifu3jn6bg"}}
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type (
|
|
Id interface {
|
|
String() string
|
|
}
|
|
Stateful struct {
|
|
Id string `json:"id"`
|
|
}
|
|
Room struct {
|
|
Rid string `json:"room_id"`
|
|
}
|
|
StatefulRoom struct {
|
|
Id string `json:"id"`
|
|
Rid string `json:"room_id"`
|
|
}
|
|
PT uint8
|
|
)
|
|
|
|
type In[I Id] struct {
|
|
Id I `json:"id,omitempty"`
|
|
T PT `json:"t"`
|
|
Payload json.RawMessage `json:"p,omitempty"` // should be json.RawMessage for 2-pass unmarshal
|
|
}
|
|
|
|
func (i In[I]) GetId() I { return i.Id }
|
|
func (i In[I]) GetPayload() []byte { return i.Payload }
|
|
func (i In[I]) GetType() PT { return i.T }
|
|
|
|
type Out struct {
|
|
Id string `json:"id,omitempty"` // string because omitempty won't work as intended with arrays
|
|
T uint8 `json:"t"`
|
|
Payload any `json:"p,omitempty"`
|
|
}
|
|
|
|
func (o *Out) SetId(s string) { o.Id = s }
|
|
func (o *Out) SetType(u uint8) { o.T = u }
|
|
func (o *Out) SetPayload(a any) { o.Payload = a }
|
|
func (o *Out) SetGetId(s fmt.Stringer) { o.Id = s.String() }
|
|
func (o *Out) GetPayload() any { return o.Payload }
|
|
|
|
// Packet codes:
|
|
//
|
|
// x, 1xx - user codes
|
|
// 15x - webrtc data exchange codes
|
|
// 2xx - worker codes
|
|
const (
|
|
CheckLatency PT = 3
|
|
InitSession PT = 4
|
|
WebrtcInit PT = 100
|
|
WebrtcOffer PT = 101
|
|
WebrtcAnswer PT = 102
|
|
WebrtcIce PT = 103
|
|
StartGame PT = 104
|
|
QuitGame PT = 105
|
|
SaveGame PT = 106
|
|
LoadGame PT = 107
|
|
ChangePlayer PT = 108
|
|
RecordGame PT = 110
|
|
GetWorkerList PT = 111
|
|
ErrNoFreeSlots PT = 112
|
|
ResetGame PT = 113
|
|
RegisterRoom PT = 201
|
|
CloseRoom PT = 202
|
|
IceCandidate = WebrtcIce
|
|
TerminateSession PT = 204
|
|
AppVideoChange PT = 150
|
|
LibNewGameList PT = 205
|
|
PrevSessions PT = 206
|
|
)
|
|
|
|
func (p PT) String() string {
|
|
switch p {
|
|
case CheckLatency:
|
|
return "CheckLatency"
|
|
case InitSession:
|
|
return "InitSession"
|
|
case WebrtcInit:
|
|
return "WebrtcInit"
|
|
case WebrtcOffer:
|
|
return "WebrtcOffer"
|
|
case WebrtcAnswer:
|
|
return "WebrtcAnswer"
|
|
case WebrtcIce:
|
|
return "WebrtcIce"
|
|
case StartGame:
|
|
return "StartGame"
|
|
case ChangePlayer:
|
|
return "ChangePlayer"
|
|
case QuitGame:
|
|
return "QuitGame"
|
|
case SaveGame:
|
|
return "SaveGame"
|
|
case LoadGame:
|
|
return "LoadGame"
|
|
case RecordGame:
|
|
return "RecordGame"
|
|
case GetWorkerList:
|
|
return "GetWorkerList"
|
|
case ErrNoFreeSlots:
|
|
return "NoFreeSlots"
|
|
case ResetGame:
|
|
return "ResetGame"
|
|
case RegisterRoom:
|
|
return "RegisterRoom"
|
|
case CloseRoom:
|
|
return "CloseRoom"
|
|
case TerminateSession:
|
|
return "TerminateSession"
|
|
case AppVideoChange:
|
|
return "AppVideoChange"
|
|
case LibNewGameList:
|
|
return "LibNewGameList"
|
|
case PrevSessions:
|
|
return "PrevSessions"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// Various codes
|
|
const (
|
|
EMPTY = ""
|
|
OK = "ok"
|
|
)
|
|
|
|
var (
|
|
ErrForbidden = fmt.Errorf("forbidden")
|
|
ErrMalformed = fmt.Errorf("malformed")
|
|
)
|
|
|
|
var (
|
|
EmptyPacket = Out{Payload: ""}
|
|
ErrPacket = Out{Payload: "err"}
|
|
OkPacket = Out{Payload: "ok"}
|
|
)
|
|
|
|
func Do[I Id, T any](in In[I], fn func(T)) error {
|
|
if dat := Unwrap[T](in.Payload); dat != nil {
|
|
fn(*dat)
|
|
return nil
|
|
}
|
|
return ErrMalformed
|
|
}
|
|
|
|
func DoE[I Id, T any](in In[I], fn func(T) error) error {
|
|
if dat := Unwrap[T](in.Payload); dat != nil {
|
|
return fn(*dat)
|
|
}
|
|
return ErrMalformed
|
|
}
|
|
|
|
func Unwrap[T any](data []byte) *T {
|
|
out := new(T)
|
|
if err := json.Unmarshal(data, out); err != nil {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
func UnwrapChecked[T any](bytes []byte, err error) (*T, error) {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Unwrap[T](bytes), nil
|
|
}
|
|
|
|
func Wrap(t any) ([]byte, error) { return json.Marshal(t) }
|
|
|
|
const separator = "___"
|
|
|
|
func ExplodeDeepLink(link string) (string, string) {
|
|
p := strings.SplitN(link, separator, 2)
|
|
|
|
if len(p) == 1 {
|
|
return p[0], ""
|
|
}
|
|
|
|
return p[0], p[1]
|
|
}
|