From 2e932ffff7acf4438fbbd0fa516f2e4a974d3d38 Mon Sep 17 00:00:00 2001 From: giongto35 Date: Sun, 22 Sep 2019 04:12:38 +0800 Subject: [PATCH 1/2] Update readme spawn docker (#89) * Update README * Update Readme typo --- Dockerfile | 1 - README.md | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 83f168b5..ba2b4ef9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ RUN apt-get update RUN apt-get install pkg-config libvpx-dev libopus-dev libopusfile-dev -y RUN mkdir -p /cloud-game -VOLUME /home/thanh/Desktop/Code/cloud-game/games:/cloud-game/games COPY . /cloud-game/ WORKDIR /cloud-game diff --git a/README.md b/README.md index 6ba8af06..7a8e8bbe 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ **Video demo**: https://www.youtube.com/watch?v=GUBrJGAxZZg ## Introduction -This project aims to bring the most modern and convenient gaming experience to users as well as experiement the performance of Cloud-gaming technology. Theoretically, games are hosted on remote servers and media are streamed to the player in an optimal way to ensure the most comfortable user interaction. It opens the ability to play any retro games on your browser directly, which are fully compatible with multi-platform like Desktop, Android, ~~IOS~~. This flexibility also enables online gaming experience to retro games. +This project aims to experiment Cloud-gaming performance with [WebRTC](https://github.com/pion/webrtc/) and [libretro](https://www.libretro.com/), as well as trying to deliver the most modern and convenient gaming experience through the technology. Theoretically, games are run on remote servers and media are streamed to the player optimally to ensure the most comfortable user interaction. It opens the ability to play any retro games on web-browser directly, which are fully compatible with multi-platform like Desktop, Android, ~~IOS~~. This flexibility also enables online gaming experience to retro games. ## Try the service at **[http://cloudretro.io](http://cloudretro.io)** -*Chrome and Chrome on Android is recommended. It's not working on Iphone and some other explorer. Click help button to see keyboard mapping.* +*Chrome and Chrome on Android is recommended. It's not working on iPhone and some other explorers. Click help button in the game UI to see keyboard mapping.* -\*In ideal network condition and less resource contention, the game will run smoothly as in the video demo. Because there might be insufficient self-host servers in US East, US West, Eu, Singapore, you may experience some latency issues + connection problem. Please retry later in less peak hours. You can try hosting your own service following the instruction the next section to have a better sense of performance. +\*In ideal network condition and less resource contention on servers, the game will run smoothly as in the video demo. Because I only hosted the platform on limited servers in US East, US West, Eu, Singapore, you may experience some latency issues + connection problem. You can try hosting the service following the instruction the next section to have a better sense of performance. Screenshot | Screenshot :-------------------------:|:-------------------------: @@ -21,13 +21,13 @@ Screenshot | Screenshot 2. Cross-platform compatibility: The game is run on web browser, the most universal built-in app. No console, plugin, external app or devices are needed. Chrome with the latest version and fully WebRTC support is recommended for the game. 3. Emulator agnostic: The game can be played directly without any extra effort to set up the gaming emulator or platform. 4. Vertically scaled: The infrastructure is designed to be able to scale under high traffic by adding more instances. -5. Cloud storage: Game state is storing on online storage, so you can come back to continue playing in a game. +5. Cloud storage: Game state is storing on online storage, so you can come back and continue playing your incomplete game later. 6. Online multiplayer: Bring online multiplayer gaming to retro games. (In Road map) 7. Collaborate gameplay: Follow the idea of "Twitch Plays Pokemon", multiple players can play the same game together (In Road map) ## Run on local by Docker -You try running the server yourself by running `make dev.run-docker`. It will spawn a docker environment and you can access the service on `localhost:8000`. +You try running the server directly by `make dev.run-docker`. It will spawn a docker environment and you can access the service on `localhost:8000`. ## Development environment @@ -47,14 +47,14 @@ brew install libvpx pkg-config opus opusfile ... not tested yet ... ``` -Because coordinator and workers needs to run simulateneously. Workers connects to coordinator. +Because the coordinator and workers need to run simultaneously. Workers connect to the coordinator. 1. Script * `make dev.run` - * The scripts spawns 2 processes one in background and one in foreground + * The scripts spawns 2 processes one in the background and one in foreground 2. Manual * Need to run coordinator and worker separately in two session - * `go run cmd/main.go -overlordhost overlord` - spawn coordinator - * `go run cmd/main.go -overlordhost ws://localhost:8000/wso` - spawn workers connecting to coordinator + * `go run cmd/overlord/main.go` - spawn coordinator + * `go run cmd/overworker/main.go --overlordhost ws://localhost:8000/wso` - spawn workers connecting to coordinator ## Wiki - [Wiki](https://github.com/giongto35/cloud-game/wiki) @@ -65,7 +65,7 @@ Because coordinator and workers needs to run simulateneously. Workers connects t ## Credits * *Pion* Webrtc team for the incredible Golang Webrtc library and their supports https://github.com/pion/webrtc/. -* *libretro/kivutar* Golang libretro https://github.com/libretro/go-nanoarch and https://retroarch.com. +* *libretro/kivutar* Golang libretro https://github.com/libretro/go-nanoarch and https://www.libretro.com/. * *gen2brain* for the h264 go encoder https://github.com/gen2brain/x264-go * *poi5305* for the video encoding https://github.com/poi5305/go-yuv2webRTC. * *fogleman* for the NES emulator https://github.com/fogleman/nes. From 474cb7592e151c094c2e3bacc62f86e45d1a06aa Mon Sep 17 00:00:00 2001 From: giongto35 Date: Sun, 22 Sep 2019 23:36:45 +0800 Subject: [PATCH 2/2] Test server (#90) * Update README * Update Readme typo * to Test server * Update libretro system path --- Makefile | 23 -------- go.mod | 2 + pkg/config/config.go | 2 - pkg/emulator/libretro/nanoarch/nanoarch.go | 2 +- pkg/overlord/config.go | 2 + pkg/overlord/handlers.go | 67 ++++++++++++++++------ pkg/overlord/overlord.go | 2 +- pkg/webrtc/webrtc.go | 3 - pkg/worker/overlord.go | 3 - 9 files changed, 56 insertions(+), 50 deletions(-) diff --git a/Makefile b/Makefile index 495ab0a1..d896d174 100644 --- a/Makefile +++ b/Makefile @@ -83,26 +83,3 @@ dev.run-docker: docker rm cloud-game-local # Overlord and worker should be run separately. docker run --privileged -v $PWD/games:/cloud-game/games -d --name cloud-game-local -p 8000:8000 -p 9000:9000 cloud-game-local bash -c "overlord --v=5 & overworker --overlordhost ws://localhost:8000/wso" - -#build: -# go build -o build/cloudretro ./cmd -# -#run: build -# # Run coordinator first -# ./build/cloudretro -overlordhost overlord & -# # Wait till overlord finish initialized -# # Run a worker connecting to overload -# ./build/cloudretro -overlordhost ws://localhost:8000/wso - -#run-docker: -# docker build . -t cloud-game-local -# docker stop cloud-game-local || true -# docker rm cloud-game-local || true -# # Overlord and worker should be run separately. Local is for demo purpose -# docker run --privileged -v $(PWD)/games:/cloud-game/games -d --name cloud-game-local -p 8000:8000 -p 9000:9000 cloud-game-local bash -c "cmd -overlordhost ws://localhost:8000/wso & cmd -overlordhost overlord" -# - -#run-fast: build-vendor -# ./build/cloudretro -overlordhost overlord & -# ./build/cloudretro -overlordhost ws://localhost:8000/wso -# diff --git a/go.mod b/go.mod index 150aad77..57d709fc 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/gorilla/websocket v1.4.0 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pion/webrtc/v2 v2.1.2 github.com/prometheus/client_golang v1.1.0 github.com/spf13/pflag v1.0.3 diff --git a/pkg/config/config.go b/pkg/config/config.go index 74529045..48cffa07 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -10,9 +10,7 @@ const DefaultSTUNTURN = `[{"urls":"stun:stun-turn.webgame2d.com:3478"},{"urls":" const CODEC_VP8 = "VP8" const CODEC_H264 = "H264" -var IsDebug = flag.Bool("debug", false, "Is game running in debug mode?") var Port = flag.String("port", "8000", "Port of the game") -var IsMonitor = flag.Bool("monitor", false, "Turn on monitor") var FrontendSTUNTURN = flag.String("stunturn", DefaultSTUNTURN, "Frontend STUN TURN servers") var Mode = flag.String("mode", "dev", "Environment") var StunTurnTemplate = `[{"urls":"stun:stun.l.google.com:19302"},{"urls":"stun:%s:3478"},{"urls":"turn:%s:3478","username":"root","credential":"root"}]` diff --git a/pkg/emulator/libretro/nanoarch/nanoarch.go b/pkg/emulator/libretro/nanoarch/nanoarch.go index d559370e..8541bd36 100644 --- a/pkg/emulator/libretro/nanoarch/nanoarch.go +++ b/pkg/emulator/libretro/nanoarch/nanoarch.go @@ -294,7 +294,7 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool { return videoSetPixelFormat(*format) case C.RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY: path := (**C.char)(data) - *path = C.CString("./libretro/system") + *path = C.CString("./pkg/emulator/libretro/system") return true case C.RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY: path := (**C.char)(data) diff --git a/pkg/overlord/config.go b/pkg/overlord/config.go index 33961704..e501ffa7 100644 --- a/pkg/overlord/config.go +++ b/pkg/overlord/config.go @@ -8,6 +8,7 @@ import ( type Config struct { Port int URLPrefix string + DebugHost string MonitoringConfig monitoring.ServerMonitoringConfig } @@ -31,6 +32,7 @@ func (c *Config) AddFlags(fs *pflag.FlagSet) *Config { fs.BoolVarP(&c.MonitoringConfig.ProfilingEnabled, "monitoring.pprof", "p", c.MonitoringConfig.ProfilingEnabled, "Enable golang pprof for server") fs.IntVarP(&c.MonitoringConfig.Port, "monitoring.port", "", c.MonitoringConfig.Port, "Monitoring server port") fs.StringVarP(&c.MonitoringConfig.URLPrefix, "monitoring.prefix", "", c.MonitoringConfig.URLPrefix, "Monitoring server url prefix") + fs.StringVarP(&c.DebugHost, "debughost", "d", "", "Specify the server want to connect directly to debug") return c } diff --git a/pkg/overlord/handlers.go b/pkg/overlord/handlers.go index ce91c5c0..d83905f5 100644 --- a/pkg/overlord/handlers.go +++ b/pkg/overlord/handlers.go @@ -24,6 +24,7 @@ const ( ) type Server struct { + cfg Config // roomToServer map roomID to workerID roomToServer map[string]string // workerClients are the map serverID to worker Client @@ -33,8 +34,9 @@ type Server struct { var upgrader = websocket.Upgrader{} var errNotFound = errors.New("Not found") -func NewServer() *Server { +func NewServer(cfg Config) *Server { return &Server{ + cfg: cfg, // Mapping serverID to client workerClients: map[string]*WorkerClient{}, // Mapping roomID to server @@ -127,30 +129,21 @@ func (o *Server) WS(w http.ResponseWriter, r *http.Request) { client := NewBrowserClient(c) go client.Listen() - // Set up server - - workerClients := o.getAvailableWorkers() - // SessionID will be the unique per frontend connection - sessionID := uuid.Must(uuid.NewV4()).String() - var serverID string - if config.MatchWorkerRandom { - serverID, err = findBestServerRandom(workerClients) - } else { - serverID, err = findBestServerFromBrowser(workerClients, client) - } - + // Get best server for frontend to connect to + workerClient, err := o.getBestWorkerClient(client) if err != nil { - log.Println(err) return } + // SessionID will be the unique per frontend connection + sessionID := uuid.Must(uuid.NewV4()).String() // Setup session wssession := &Session{ ID: sessionID, handler: o, BrowserClient: client, - WorkerClient: o.workerClients[serverID], - ServerID: serverID, + WorkerClient: workerClient, + ServerID: workerClient.ServerID, } // TODO:? // defer wssession.Close() @@ -161,7 +154,7 @@ func (o *Server) WS(w http.ResponseWriter, r *http.Request) { wssession.BrowserClient.Send(cws.WSPacket{ ID: "init", - Data: createInitPackage(o.workerClients[serverID].StunTurnServer), + Data: createInitPackage(workerClient.StunTurnServer), }, nil) // If peerconnection is done (client.Done is signalled), we close peerconnection @@ -178,6 +171,35 @@ func (o *Server) WS(w http.ResponseWriter, r *http.Request) { wssession.WorkerClient.IsAvailable = true } +func (o *Server) getBestWorkerClient(client *BrowserClient) (*WorkerClient, error) { + if o.cfg.DebugHost != "" { + log.Println("Connecting to debug host instead prod servers", o.cfg.DebugHost) + wc := o.getWorkerFromAddress(o.cfg.DebugHost) + if wc != nil { + return wc, nil + } + // if there is not debugHost, continue usual flow + log.Println("Not found, connecting to all available servers") + } + + workerClients := o.getAvailableWorkers() + + var serverID string + var err error + if config.MatchWorkerRandom { + serverID, err = findBestServerRandom(workerClients) + } else { + serverID, err = findBestServerFromBrowser(workerClients, client) + } + + if err != nil { + log.Println(err) + return nil, err + } + + return o.workerClients[serverID], nil +} + // getAvailableWorkers returns the list of available worker func (o *Server) getAvailableWorkers() map[string]*WorkerClient { workerClients := map[string]*WorkerClient{} @@ -190,6 +212,17 @@ func (o *Server) getAvailableWorkers() map[string]*WorkerClient { return workerClients } +// getWorkerFromAddress returns the worker has given address +func (o *Server) getWorkerFromAddress(address string) *WorkerClient { + for _, w := range o.workerClients { + if w.IsAvailable && w.Address == address { + return w + } + } + + return nil +} + // findBestServer returns the best server for a session func findBestServerRandom(workerClients map[string]*WorkerClient) (string, error) { // TODO: Find best Server by latency, currently return by ping diff --git a/pkg/overlord/overlord.go b/pkg/overlord/overlord.go index 45285e66..7b2b704e 100644 --- a/pkg/overlord/overlord.go +++ b/pkg/overlord/overlord.go @@ -47,7 +47,7 @@ func (o *Overlord) Shutdown() { // initializeOverlord setup an overlord server func (o *Overlord) initializeOverlord() { - overlord := NewServer() + overlord := NewServer(o.cfg) http.HandleFunc("/", overlord.GetWeb) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./web")))) diff --git a/pkg/webrtc/webrtc.go b/pkg/webrtc/webrtc.go index 8cc5aad8..ee7b5002 100644 --- a/pkg/webrtc/webrtc.go +++ b/pkg/webrtc/webrtc.go @@ -271,9 +271,6 @@ func (w *WebRTC) startStreaming(vp8Track *webrtc.Track, opusTrack *webrtc.Track) }() for data := range w.ImageChannel { - if *config.IsMonitor { - log.Println("Encoding FPS : ", w.calculateFPS()) - } err := vp8Track.WriteSample(media.Sample{Data: data, Samples: 1}) if err != nil { log.Println("Warn: Err write sample: ", err) diff --git a/pkg/worker/overlord.go b/pkg/worker/overlord.go index 1103829a..fb3bb370 100644 --- a/pkg/worker/overlord.go +++ b/pkg/worker/overlord.go @@ -2,7 +2,6 @@ package worker import ( "encoding/json" - "fmt" "log" "github.com/giongto35/cloud-game/pkg/cws" @@ -57,12 +56,10 @@ func (h *Handler) RouteOverlord() { SDP string `json:"sdp"` IsMobile bool `json:"is_mobile"` } - fmt.Println("HIHIHIHI!!!!", resp.Data) err := json.Unmarshal([]byte(resp.Data), &initPacket) if err != nil { panic(err) } - fmt.Println("HIHIHIHI!!!!", initPacket) localSession, err := peerconnection.StartClient(initPacket.SDP, initPacket.IsMobile, iceCandidates[resp.SessionID]) // h.peerconnections[resp.SessionID] = peerconnection