diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..d929f1f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* linguist-vendored +*.go linguist-vendored=false diff --git a/.gitignore b/.gitignore index 2127e06b..0f1f2478 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ Network Trash Folder Temporary Items .apdisk +### Production +DockerfileProd +prod/ diff --git a/Dockerfile b/Dockerfile index baa2b293..79c56090 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get update RUN apt-get install pkg-config libvpx-dev libopus-dev libopusfile-dev -y RUN go get github.com/pion/webrtc RUN go get github.com/gorilla/websocket +RUN go get github.com/satori/go.uuid RUN go install github.com/giongto35/cloud-game EXPOSE 8000 diff --git a/README.md b/README.md index e51c68f5..cce9b7b0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Cloud Gaming Service Lite -Cloud Gaming Service is an open source Cloud Gaming Service building on [WebRTC](https://github.com/pion). +Cloud Gaming Service Lite is an open source Cloud Gaming Service building on [WebRTC](https://github.com/pion). -With cloud gaming, you can play any of your favourite NES game directly on your browser without installing it. It also brings modern online multiplayer gaming experience to classic NES game, so two people can play the game together. +With cloud gaming, you can play any of your favourite NES games directly on your browser without installing it. It also brings modern online multiplayer gaming experience to classic NES game, so two people can play the game together. ![screenshot](static/img/landing-page.png) @@ -34,13 +34,15 @@ US East: (Haven't hosted) Europe: (Haven't hosted) * [http://eu.nes.webgame2d.com](http://eu.nes.webgame2d.com) -* [http://eu.nes.playcloud.games](http://eu.nes.playcloud.games) +* [http://eu.nes.playcloud.games](http://eu.nes.playcloud.games) + +Note: The current state of cloud gaming service lite are not optimized for production. The service will still experience lag in the case of heavy traffic. You can try hosting your own service following the instruction in the next session. ## Run on local You can host the server yourself by running `./run_local.sh`. It will spawn a docker environment and you can access the emulator on `localhost:8000`. -You can open port, so other person can access your local machine and play the game together. +You can open port on your machine, so other person can access your local machine and play the game together. ## Development environment diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..3f03b0d4 --- /dev/null +++ b/config/config.go @@ -0,0 +1,10 @@ +package config + +import "flag" + +const defaultoverlord = "ws://localhost:9000/wso" + +var IsDebug = flag.Bool("debug", false, "Is game running in debug mode?") +var OverlordHost = flag.String("overlordhost", defaultoverlord, "Specify the path for overlord. If the flag is `overlord`, the server will be run as overlord") +var Port = flag.String("port", "8000", "Port of the game") +var IsMonitor = flag.Bool("monitor", false, "Turn on monitor") diff --git a/main.go b/main.go index 51a36d18..5a428bdc 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,8 @@ package main import ( - "os" "encoding/json" - "fmt" + "flag" "image" "io/ioutil" "log" @@ -11,80 +10,104 @@ import ( "net/http" _ "net/http/pprof" "strconv" + "strings" + "sync" "time" + "github.com/giongto35/cloud-game/config" "github.com/giongto35/cloud-game/ui" "github.com/giongto35/cloud-game/util" "github.com/giongto35/cloud-game/webrtc" "github.com/gorilla/websocket" pionRTC "github.com/pion/webrtc" - - "gopkg.in/hraban/opus.v2" + uuid "github.com/satori/go.uuid" ) - const ( - width = 256 - height = 240 - scale = 3 - title = "NES" + width = 256 + height = 240 + scale = 3 + title = "NES" gameboyIndex = "./static/gameboy.html" - debugIndex = "./static/index_ws.html" + debugIndex = "./static/index_ws.html" ) - var indexFN = gameboyIndex // Time allowed to write a message to the peer. var readWait = 30 * time.Second var writeWait = 30 * time.Second +var IsOverlord = false var upgrader = websocket.Upgrader{} -type WSPacket struct { - ID string `json:"id"` - Data string `json:"data"` - RoomID string `json:"room_id"` - PlayerIndex int `json:"player_index"` -} - // Room is a game session. multi webRTC sessions can connect to a same game. // A room stores all the channel for interaction between all webRTCs session and emulator type Room struct { imageChannel chan *image.RGBA - audioChanel chan float32 + audioChannel chan float32 inputChannel chan int - // closedChannel is to fire exit event when there is no webRTC session running - closedChannel chan bool + // Done channel is to fire exit event when there is no webRTC session running + Done chan struct{} - rtcSessions []*webrtc.WebRTC + rtcSessions []*webrtc.WebRTC + sessionsLock *sync.Mutex director *ui.Director } -var rooms map[string]*Room +var rooms = map[string]*Room{} + +// ID to peerconnection +var peerconnections = map[string]*webrtc.WebRTC{} +var serverID = "" +var oclient *Client func main() { - fmt.Println("Usage: ./game [debug]") - if len(os.Args) > 1 { + flag.Parse() + log.Println("Usage: ./game [debug]") + if *config.IsDebug { // debug indexFN = debugIndex - fmt.Println("Use debug version") + log.Println("Use debug version") + } + + if *config.OverlordHost == "overlord" { + log.Println("Running as overlord ") + IsOverlord = true + } else { + if strings.HasPrefix(*config.OverlordHost, "ws") && !strings.HasSuffix(*config.OverlordHost, "wso") { + log.Fatal("Overlord connection is invalid. Should have the form `ws://.../wso`") + } + log.Println("Running as slave ") + IsOverlord = false } rand.Seed(time.Now().UTC().UnixNano()) - fmt.Println("http://localhost:8000") rooms = map[string]*Room{} // ignore origin upgrader.CheckOrigin = func(r *http.Request) bool { return true } - http.HandleFunc("/ws", ws) http.HandleFunc("/", getWeb) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) - http.ListenAndServe(":8000", nil) -} + http.HandleFunc("/ws", ws) + if !IsOverlord { + oclient = NewOverlordClient() + } + + log.Println("oclient ", oclient) + if !IsOverlord { + log.Println("http://localhost:" + *config.Port) + http.ListenAndServe(":"+*config.Port, nil) + } else { + log.Println("http://localhost:9000") + // Overlord expose one more path for handle overlord connections + http.HandleFunc("/wso", wso) + http.ListenAndServe(":9000", nil) + } +} func getWeb(w http.ResponseWriter, r *http.Request) { bs, err := ioutil.ReadFile(indexFN) @@ -100,25 +123,26 @@ func initRoom(roomID, gameName string) string { if roomID == "" { roomID = generateRoomID() } + log.Println("Init new room", roomID) imageChannel := make(chan *image.RGBA, 100) - audioChannel := make(chan float32, 48000) + audioChannel := make(chan float32, ui.SampleRate) inputChannel := make(chan int, 100) - closedChannel := make(chan bool) // create director - director := ui.NewDirector(roomID, imageChannel, audioChannel, inputChannel, closedChannel) - - rooms[roomID] = &Room{ - imageChannel: imageChannel, - audioChanel: audioChannel, - inputChannel: inputChannel, - closedChannel: closedChannel, - rtcSessions: []*webrtc.WebRTC{}, - director: director, - } + director := ui.NewDirector(roomID, imageChannel, audioChannel, inputChannel) - go fanoutScreen(imageChannel, roomID) - go fanoutAudio(audioChannel, roomID) + room := &Room{ + imageChannel: imageChannel, + audioChannel: audioChannel, + inputChannel: inputChannel, + rtcSessions: []*webrtc.WebRTC{}, + sessionsLock: &sync.Mutex{}, + director: director, + Done: make(chan struct{}), + } + rooms[roomID] = room + + go room.start() go director.Start([]string{"games/" + gameName}) return roomID @@ -142,21 +166,35 @@ func isRoomRunning(roomID string) bool { } // startSession handles one session call -func startSession(webRTC *webrtc.WebRTC, gameName string, roomID string, playerIndex int) string { +func startSession(webRTC *webrtc.WebRTC, gameName string, roomID string, playerIndex int) (rRoomID string, isNewRoom bool) { + isNewRoom = false + cleanSession(webRTC) // If the roomID is empty, // or the roomID doesn't have any running sessions (room was closed) // we spawn a new room if roomID == "" || !isRoomRunning(roomID) { roomID = initRoom(roomID, gameName) + isNewRoom = true } // TODO: Might have race condition rooms[roomID].rtcSessions = append(rooms[roomID].rtcSessions, webRTC) - go faninInput(rooms[roomID].inputChannel, webRTC, playerIndex) + room := rooms[roomID] - return roomID + webRTC.AttachRoomID(roomID) + go startWebRTCSession(room, webRTC, playerIndex) + + return roomID, isNewRoom } +// Session represents a session connected from the browser to the current server +type Session struct { + client *Client + peerconnection *webrtc.WebRTC + ServerID string +} + +// Handle normal traffic (from browser to host) func ws(w http.ResponseWriter, r *http.Request) { c, err := upgrader.Upgrade(w, r, nil) if err != nil { @@ -164,223 +202,176 @@ func ws(w http.ResponseWriter, r *http.Request) { return } defer c.Close() - - log.Println("New ws connection") - webRTC := webrtc.NewWebRTC() - - // streaming game - - // start new games and webrtc stuff? - isDone := false - var gameName string var roomID string var playerIndex int - for !isDone { - c.SetReadDeadline(time.Now().Add(readWait)) - mt, message, err := c.ReadMessage() - if err != nil { - log.Println("[!] read:", err) - break - } - - req := WSPacket{} - res := WSPacket{} - - err = json.Unmarshal(message, &req) - if err != nil { - log.Println("[!] json unmarshal:", err) - break - } - - // SDP connection initializations follows WebRTC convention - // https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols - switch req.ID { - case "ping": - gameName = req.Data - roomID = req.RoomID - playerIndex = req.PlayerIndex - log.Println("Ping from server with game:", gameName) - res.ID = "pong" - - case "sdp": - log.Println("Received user SDP") - localSession, err := webRTC.StartClient(req.Data, width, height) - if err != nil { - log.Fatalln(err) - } - - res.ID = "sdp" - res.Data = localSession - - case "candidate": - // Unuse code - hi := pionRTC.ICECandidateInit{} - err = json.Unmarshal([]byte(req.Data), &hi) - if err != nil { - log.Println("[!] Cannot parse candidate: ", err) - } else { - // webRTC.AddCandidate(hi) - } - res.ID = "candidate" - - case "start": - log.Println("Starting game") - roomID = startSession(webRTC, gameName, roomID, playerIndex) - res.ID = "start" - res.RoomID = roomID - - // maybe we wont close websocket - // isDone = true - - case "save": - log.Println("Saving game state") - res.ID = "save" - res.Data = "ok" - if roomID != "" { - err = rooms[roomID].director.SaveGame() - if err != nil { - log.Println("[!] Cannot save game state: ", err) - res.Data = "error" - } - } else { - res.Data = "error" - } - - case "load": - log.Println("Loading game state") - res.ID = "load" - res.Data = "ok" - if roomID != "" { - err = rooms[roomID].director.LoadGame() - if err != nil { - log.Println("[!] Cannot load game state: ", err) - res.Data = "error" - } - } else { - res.Data = "error" - } - } - - stRes, err := json.Marshal(res) - if err != nil { - log.Println("json marshal:", err) - } - - c.SetWriteDeadline(time.Now().Add(writeWait)) - err = c.WriteMessage(mt, []byte(stRes)) - if err != nil { - log.Println("write:", err) - break - } + // Create connection to overlord + client := NewClient(c) + //sessionID := strconv.Itoa(rand.Int()) + sessionID := uuid.Must(uuid.NewV4()).String() + wssession := &Session{ + client: client, + peerconnection: webrtc.NewWebRTC(), + // The server session is maintaining } + + client.receive("heartbeat", func(resp WSPacket) WSPacket { + return resp + }) + + client.receive("initwebrtc", func(resp WSPacket) WSPacket { + log.Println("Received user SDP") + localSession, err := wssession.peerconnection.StartClient(resp.Data, width, height) + if err != nil { + log.Fatalln(err) + } + + return WSPacket{ + ID: "sdp", + Data: localSession, + SessionID: sessionID, + } + }) + + client.receive("save", func(resp WSPacket) (req WSPacket) { + log.Println("Saving game state") + req.ID = "save" + req.Data = "ok" + if roomID != "" { + err = rooms[roomID].director.SaveGame() + if err != nil { + log.Println("[!] Cannot save game state: ", err) + req.Data = "error" + } + } else { + req.Data = "error" + } + + return req + }) + + client.receive("load", func(resp WSPacket) (req WSPacket) { + log.Println("Loading game state") + req.ID = "load" + req.Data = "ok" + if roomID != "" { + err = rooms[roomID].director.LoadGame() + if err != nil { + log.Println("[!] Cannot load game state: ", err) + req.Data = "error" + } + } else { + req.Data = "error" + } + + return req + }) + + client.receive("start", func(resp WSPacket) (req WSPacket) { + gameName = resp.Data + roomID = resp.RoomID + playerIndex = resp.PlayerIndex + isNewRoom := false + + log.Println("Starting game") + // If we are connecting to overlord, request serverID from roomID + if oclient != nil { + roomServerID := getServerIDOfRoom(oclient, roomID) + log.Println("Server of RoomID ", roomID, " is ", roomServerID) + if roomServerID != "" && wssession.ServerID != roomServerID { + // TODO: Re -register + go bridgeConnection(wssession, roomServerID, gameName, roomID, playerIndex) + return + } + } + + roomID, isNewRoom = startSession(wssession.peerconnection, gameName, roomID, playerIndex) + // Register room to overlord if we are connecting to overlord + if isNewRoom && oclient != nil { + oclient.send(WSPacket{ + ID: "registerRoom", + Data: roomID, + }, nil) + } + req.ID = "start" + req.RoomID = roomID + req.SessionID = sessionID + + return req + }) + + client.receive("candidate", func(resp WSPacket) (req WSPacket) { + // Unuse code + hi := pionRTC.ICECandidateInit{} + err = json.Unmarshal([]byte(resp.Data), &hi) + if err != nil { + log.Println("[!] Cannot parse candidate: ", err) + } else { + // webRTC.AddCandidate(hi) + } + req.ID = "candidate" + + return req + }) + + client.listen() } // generateRoomID generate a unique room ID containing 16 digits func generateRoomID() string { roomID := strconv.FormatInt(rand.Int63(), 16) + //roomID := uuid.Must(uuid.NewV4()).String() return roomID } -// fanoutScreen fanout outputs to all webrtc in the same room -func fanoutScreen(imageChannel chan *image.RGBA, roomID string) { - for image := range imageChannel { - isRoomRunning := false - - yuv := util.RgbaToYuv(image) - for _, webRTC := range rooms[roomID].rtcSessions { - // Client stopped - if webRTC.IsClosed() { - continue - } - - // encode frame - // fanout imageChannel - if webRTC.IsConnected() { - // NOTE: can block here - webRTC.ImageChannel <- yuv - } - isRoomRunning = true - } - - if isRoomRunning == false { - log.Println("Closed room from screen routine", roomID) - rooms[roomID].closedChannel <- true - return - } - } -} - - -// fanoutAudio fanout outputs to all webrtc in the same room -func fanoutAudio(audioChannel chan float32, roomID string) { - log.Println("Enter fan audio") - - enc, err := opus.NewEncoder(ui.SampleRate, ui.Channels, opus.AppAudio) - - maxBufferSize := ui.TimeFrame * ui.SampleRate / 1000 - pcm := make([]float32, maxBufferSize) // 640 * 1000 / 16000 == 40 ms - idx := 0 - - if err != nil { - log.Println("[!] Cannot create audio encoder") - return - } - - var count byte = 0 - +func (r *Room) start() { + // fanout Screen for { - pcm[idx] = <- audioChannel - idx ++ + select { + case <-r.Done: + r.remove() + return + case image := <-r.imageChannel: + //isRoomRunning := false - if idx == len(pcm) { - data := make([]byte, 640) - - n, err := enc.EncodeFloat32(pcm, data) - - if err != nil { - log.Println("[!] Failed to decode") - continue - } - data = data[:n] - // data = append(data, count) - - - isRoomRunning := false - for _, webRTC := range rooms[roomID].rtcSessions { + yuv := util.RgbaToYuv(image) + r.sessionsLock.Lock() + for _, webRTC := range r.rtcSessions { // Client stopped if webRTC.IsClosed() { continue } - + // encode frame // fanout imageChannel if webRTC.IsConnected() { // NOTE: can block here - webRTC.AudioChannel <- data + webRTC.ImageChannel <- yuv } - isRoomRunning = true + //isRoomRunning = true } - - if isRoomRunning == false { - log.Println("Closed room from audio routine", roomID) - rooms[roomID].closedChannel <- true - return - } - idx = 0 - count = (count + 1) & 0xff + r.sessionsLock.Unlock() } - } } +func (r *Room) remove() { + log.Println("Closing room", r) + r.director.Done <- struct{}{} +} - -// faninInput fan-in of the same room to inputChannel -func faninInput(inputChannel chan int, webRTC *webrtc.WebRTC, playerIndex int) { +// startWebRTCSession fan-in of the same room to inputChannel +func startWebRTCSession(room *Room, webRTC *webrtc.WebRTC, playerIndex int) { + inputChannel := room.inputChannel + log.Println("room, inputChannel", room, inputChannel) for { + select { + case <-webRTC.Done: + removeSession(webRTC, room) + default: + } // Client stopped if webRTC.IsClosed() { return @@ -397,3 +388,157 @@ func faninInput(inputChannel chan int, webRTC *webrtc.WebRTC, playerIndex int) { } } } + +func cleanSession(w *webrtc.WebRTC) { + room, ok := rooms[w.RoomID] + if !ok { + return + } + removeSession(w, room) +} + +func removeSession(w *webrtc.WebRTC, room *Room) { + room.sessionsLock.Lock() + defer room.sessionsLock.Unlock() + for i, s := range room.rtcSessions { + if s == w { + room.rtcSessions = append(room.rtcSessions[:i], room.rtcSessions[i+1:]...) + break + } + } + // If room has no sessions, close room + if len(room.rtcSessions) == 0 { + room.Done <- struct{}{} + } +} + +func getServerIDOfRoom(oc *Client, roomID string) string { + log.Println("Request overlord roomID") + packet := oc.syncSend( + WSPacket{ + ID: "getRoom", + Data: roomID, + }, + ) + log.Println("Received roomID from overlord") + + return packet.Data +} + +func bridgeConnection(session *Session, serverID string, gameName string, roomID string, playerIndex int) { + log.Println("Bridging connection to other Host ", serverID) + client := session.client + // Ask client to init + + log.Println("Requesting offer to browser", serverID) + resp := client.syncSend(WSPacket{ + ID: "requestOffer", + Data: "", + }) + + log.Println("Sending offer to overlord to relay message to target host", resp.TargetHostID) + // Ask overlord to relay SDP packet to serverID + resp.TargetHostID = serverID + remoteTargetSDP := oclient.syncSend(resp) + log.Println("Got back remote host SDP, sending to browser") + // Send back remote SDP of remote server to browser + //client.syncSend(WSPacket{ + //ID: "sdp", + //Data: remoteTargetSDP.Data, + //}) + client.send(WSPacket{ + ID: "sdp", + Data: remoteTargetSDP.Data, + }, nil) + log.Println("Init session done, start game on target host") + + oclient.syncSend(WSPacket{ + ID: "start", + Data: gameName, + TargetHostID: serverID, + RoomID: roomID, + PlayerIndex: playerIndex, + }) + log.Println("Game is started on remote host") +} + +func createOverlordConnection() (*websocket.Conn, error) { + c, _, err := websocket.DefaultDialer.Dial(*config.OverlordHost, nil) + if err != nil { + return nil, err + } + + return c, nil +} + +func NewOverlordClient() *Client { + oc, err := createOverlordConnection() + if err != nil { + log.Println("Cannot connect to overlord") + log.Println("Run as a single server") + return nil + } + oclient := NewClient(oc) + + // Received from overlord the serverID + oclient.receive( + "serverID", + func(response WSPacket) (request WSPacket) { + // Stick session with serverID got from overlord + log.Println("Received serverID ", response.Data) + serverID = response.Data + + return EmptyPacket + }, + ) + + // Received from overlord the sdp. This is happens when bridging + // TODO: refactor + oclient.receive( + "initwebrtc", + func(resp WSPacket) (req WSPacket) { + log.Println("Received a sdp request from overlord") + log.Println("Start peerconnection from the sdp") + peerconnection := webrtc.NewWebRTC() + // init new peerconnection from sessionID + localSession, err := peerconnection.StartClient(resp.Data, width, height) + peerconnections[resp.SessionID] = peerconnection + + if err != nil { + log.Fatalln(err) + } + + return WSPacket{ + ID: "sdp", + Data: localSession, + } + }, + ) + + // Received start from overlord. This is happens when bridging + // TODO: refactor + oclient.receive( + "start", + func(resp WSPacket) (req WSPacket) { + log.Println("Received a start request from overlord") + log.Println("Add the connection to current room on the host") + + peerconnection := peerconnections[resp.SessionID] + roomID, isNewRoom := startSession(peerconnection, resp.Data, resp.RoomID, resp.PlayerIndex) + // Bridge always access to old room + // TODO: log warn + if isNewRoom == true { + log.Fatal("Bridge should not spawn new room") + } + + req.ID = "start" + req.RoomID = roomID + return req + }, + ) + // heartbeat to keep pinging overlord. We not ping from server to browser, so we don't call heartbeat in browserClient + go oclient.heartbeat() + go oclient.listen() + + return oclient +} \ No newline at end of file diff --git a/overlord.go b/overlord.go new file mode 100644 index 00000000..4a9795f8 --- /dev/null +++ b/overlord.go @@ -0,0 +1,123 @@ +package main + +import ( + "fmt" + "log" + "math/rand" + "net/http" + "strconv" + + "github.com/giongto35/cloud-game/webrtc" +) + +var roomToServer = map[string]string{} + +// servers are the map serverID to server Client +var servers = map[string]*Client{} + +// If it's overlord, handle overlord connection (from host to overlord) +func wso(w http.ResponseWriter, r *http.Request) { + fmt.Println("Connected") + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("[!] WS upgrade:", err) + return + } + defer c.Close() + + // register new server + serverID := strconv.Itoa(rand.Int()) + log.Println("A new server connected ", serverID) + + client := NewClient(c) + servers[serverID] = client + + wssession := &Session{ + client: client, + peerconnection: webrtc.NewWebRTC(), + // The server session is maintaining + } + + client.send( + WSPacket{ + ID: "serverID", + Data: serverID, + }, + nil, + ) + + client.receive("registerRoom", func(resp WSPacket) WSPacket { + log.Println("Received registerRoom ", resp.Data, serverID) + roomToServer[resp.Data] = serverID + return WSPacket{ + ID: "registerRoom", + } + }) + + client.receive("getRoom", func(resp WSPacket) WSPacket { + log.Println("Received a getroom request") + return WSPacket{ + ID: "getRoom", + Data: roomToServer[resp.Data], + } + }) + + client.receive("initwebrtc", func(resp WSPacket) WSPacket { + log.Println("Received a relay sdp request from a host") + // TODO: Abstract + if resp.TargetHostID != serverID { + log.Println("sending relay sdp to target host", resp) + // relay SDP to target host and get back sdp + // TODO: Async + sdp := servers[resp.TargetHostID].syncSend( + resp, + ) + + return sdp + } + log.Println("Target host is overlord itself: start peerconnection") + // If the target is in master + // start by its old + localSession, err := wssession.peerconnection.StartClient(resp.Data, width, height) + if err != nil { + log.Fatalln(err) + } + + return WSPacket{ + ID: "sdp", + Data: localSession, + } + }) + + // TODO: use relay ID type + // TODO: Merge sdp and start + client.receive("start", func(resp WSPacket) WSPacket { + log.Println("Received a relay start request from a host") + // TODO: Abstract + if resp.TargetHostID != serverID { + // relay SDP to target host and get back sdp + // TODO: Async + resp := servers[resp.TargetHostID].syncSend( + resp, + ) + + return resp + } + log.Println("Target host is overlord itself: start game") + // If the target is in master + // start by its old + roomID, isNewRoom := startSession(wssession.peerconnection, resp.Data, resp.RoomID, resp.PlayerIndex) + // Bridge always access to old room + // TODO: log warn + if isNewRoom == true { + log.Fatal("Bridge should not spawn new room") + } + + return WSPacket{ + ID: "start", + RoomID: roomID, + } + }) + + client.listen() +} diff --git a/overlord/overlord.go b/overlord/overlord.go new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/overlord/overlord.go @@ -0,0 +1 @@ + diff --git a/run_local.sh b/run_local.sh index 3f09706c..b74cba78 100755 --- a/run_local.sh +++ b/run_local.sh @@ -2,4 +2,4 @@ docker build . -t cloud-game-local docker stop cloud-game-local docker rm cloud-game-local -docker run --privileged -d --name cloud-game-local -p 8000:8000 cloud-game +docker run --privileged -d --name cloud-game-local -p 8000:8000 cloud-game-local cloud-game diff --git a/static/gameboy.html b/static/gameboy.html index 5f7684a7..3aff8ee2 100644 --- a/static/gameboy.html +++ b/static/gameboy.html @@ -37,7 +37,8 @@
GAME BOY
C
-