Merge with master

This commit is contained in:
giongto35 2019-09-22 23:38:42 +08:00
commit 6bf2db51ac
11 changed files with 61 additions and 56 deletions

1
Dockerfile vendored
View file

@ -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

23
Makefile vendored
View file

@ -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
#

10
README.md vendored
View file

@ -3,13 +3,13 @@
**Video demo**: https://www.youtube.com/watch?v=GUBrJGAxZZg
## Introduction
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 in an optimal way 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.
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 explorers. Click help button in the game UI 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 on servers, the game will run smoothly as in the video demo. Because I only hosted the platform on a limited 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
:-------------------------:|:-------------------------:
@ -47,10 +47,10 @@ 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/overlord/main.go` - spawn coordinator

2
go.mod vendored
View file

@ -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

View file

@ -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"}]`

View file

@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -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"))))

View file

@ -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)

View file

@ -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