mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-25 19:13:52 +00:00
Move gamelist to become a utility library, this package can be shared between overlord and worker (#72)
* Return games based on type * Add nes extension also * Update game list logic * Add more files to diff
This commit is contained in:
parent
0790a6d5c3
commit
29d718de08
11 changed files with 126 additions and 90 deletions
|
|
@ -20,8 +20,6 @@ import (
|
|||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
const gamePath = "games"
|
||||
|
||||
// Time allowed to write a message to the peer.
|
||||
var upgrader = websocket.Upgrader{}
|
||||
|
||||
|
|
@ -49,7 +47,7 @@ func initilizeOverlord() {
|
|||
|
||||
// initializeWorker setup a worker
|
||||
func initializeWorker() {
|
||||
worker := worker.NewHandler(*config.OverlordHost, gamePath)
|
||||
worker := worker.NewHandler(*config.OverlordHost)
|
||||
|
||||
defer func() {
|
||||
log.Println("Close worker")
|
||||
|
|
@ -103,6 +101,7 @@ func main() {
|
|||
if *config.IsMonitor {
|
||||
go monitor()
|
||||
}
|
||||
|
||||
// There are two server mode
|
||||
// Overlord is coordinator. If the OvelordHost Param is `overlord`, we spawn a new host as Overlord.
|
||||
// else we spawn new server as normal server connecting to OverlordHost.
|
||||
|
|
|
|||
|
|
@ -24,3 +24,9 @@ var MatchWorkerRandom = false
|
|||
var ProdEnv = "prod"
|
||||
|
||||
var Codec = CODEC_H264
|
||||
var FileTypeToEmulator = map[string]string{
|
||||
"gba": "gba",
|
||||
"cue": "pcsx",
|
||||
"zip": "mame",
|
||||
"nes": "nes",
|
||||
}
|
||||
|
|
|
|||
2
go.mod
vendored
2
go.mod
vendored
|
|
@ -10,6 +10,6 @@ require (
|
|||
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 // indirect
|
||||
github.com/prometheus/client_golang v1.1.0
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20180426093920-0f2e0b4fc6cd
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package gamelist
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// getGameList returns list of games stored in games
|
||||
// TODO: change to class
|
||||
func GetGameList(gamePath string) []string {
|
||||
var games []string
|
||||
filepath.Walk(gamePath, func(path string, info os.FileInfo, err error) error {
|
||||
if info != nil && !info.IsDir() {
|
||||
// Remove prefix to obtain file names
|
||||
path = path[len(gamePath)+1:]
|
||||
// Add to games list
|
||||
games = append(games, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return games
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ import (
|
|||
|
||||
"github.com/giongto35/cloud-game/config"
|
||||
"github.com/giongto35/cloud-game/cws"
|
||||
"github.com/giongto35/cloud-game/overlord/gamelist"
|
||||
"github.com/giongto35/cloud-game/util"
|
||||
"github.com/giongto35/cloud-game/util/gamelist"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
|
@ -25,6 +25,7 @@ const (
|
|||
)
|
||||
|
||||
type Server struct {
|
||||
// roomToServer map roomID to workerID
|
||||
roomToServer map[string]string
|
||||
// workerClients are the map serverID to worker Client
|
||||
workerClients map[string]*WorkerClient
|
||||
|
|
@ -161,7 +162,7 @@ func (o *Server) WS(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
wssession.BrowserClient.Send(cws.WSPacket{
|
||||
ID: "init",
|
||||
Data: createInitPackage(o.workerClients[serverID].StunTurnServer, gamePath),
|
||||
Data: createInitPackage(o.workerClients[serverID].StunTurnServer),
|
||||
}, nil)
|
||||
|
||||
// If peerconnection is done (client.Done is signalled), we close peerconnection
|
||||
|
|
@ -290,9 +291,13 @@ func (o *Server) cleanConnection(client *WorkerClient, serverID string) {
|
|||
|
||||
// createInitPackage returns serverhost + game list in encoded wspacket format
|
||||
// This package will be sent to initialize
|
||||
func createInitPackage(stunturn, gamePath string) string {
|
||||
gameList := gamelist.GetGameList(gamePath)
|
||||
initPackage := append([]string{stunturn}, gameList...)
|
||||
func createInitPackage(stunturn string) string {
|
||||
var gameName []string
|
||||
for _, game := range gamelist.GameList {
|
||||
gameName = append(gameName, game.Name)
|
||||
}
|
||||
|
||||
initPackage := append([]string{stunturn}, gameName...)
|
||||
encodedList, _ := json.Marshal(initPackage)
|
||||
return string(encodedList)
|
||||
}
|
||||
|
|
|
|||
6
static/js/controller.js
vendored
6
static/js/controller.js
vendored
|
|
@ -37,14 +37,14 @@ function reloadGameMenu() {
|
|||
|
||||
// sort gameList first
|
||||
gameList.sort(function (a, b) {
|
||||
return a.name > b.name ? 1 : -1;
|
||||
return a > b ? 1 : -1;
|
||||
});
|
||||
|
||||
// generate html
|
||||
var listbox = $("#menu-container");
|
||||
listbox.html('');
|
||||
gameList.forEach(function (game) {
|
||||
listbox.append(`<div class="menu-item unselectable" unselectable="on"><div><span>${game.name}</span></div></div>`);
|
||||
listbox.append(`<div class="menu-item unselectable" unselectable="on"><div><span>${game}</span></div></div>`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ function pickGame(idx) {
|
|||
$(`.menu-item:eq(${idx}) span`).addClass("pick");
|
||||
|
||||
gameIdx = idx;
|
||||
log(`> [Pick] game ${gameIdx + 1}/${gameList.length} - ${gameList[gameIdx].name}`);
|
||||
log(`> [Pick] game ${gameIdx + 1}/${gameList.length} - ${gameList[gameIdx]}`);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
11
static/js/ws.js
vendored
11
static/js/ws.js
vendored
|
|
@ -35,10 +35,8 @@ conn.onmessage = e => {
|
|||
data.shift()
|
||||
gameList = [];
|
||||
|
||||
data.forEach(file => {
|
||||
var file = file
|
||||
var name = file.substr(0, file.indexOf('.'));
|
||||
gameList.push({file: file, name: name});
|
||||
data.forEach(name => {
|
||||
gameList.push(name);
|
||||
});
|
||||
|
||||
log("Received game list");
|
||||
|
|
@ -63,7 +61,7 @@ conn.onmessage = e => {
|
|||
// TODO: Calc time
|
||||
break;
|
||||
case "start":
|
||||
roomID = d["room_id"];
|
||||
roomID = d["room_id"];
|
||||
log(`Got start with room id: ${roomID}`);
|
||||
popup("Started! You can share you game!")
|
||||
saveRoomID(roomID);
|
||||
|
|
@ -272,8 +270,7 @@ function startGame() {
|
|||
log("Starting game screen");
|
||||
screenState = "game";
|
||||
|
||||
// conn.send(JSON.stringify({"id": "start", "data": gameList[gameIdx].file, "room_id": $("#room-txt").val(), "player_index": parseInt(playerIndex.value, 10)}));
|
||||
conn.send(JSON.stringify({"id": "start", "data": gameList[gameIdx].file, "room_id": roomID != null ? roomID : '', "player_index": 1}));
|
||||
conn.send(JSON.stringify({"id": "start", "data": gameList[gameIdx], "room_id": roomID != null ? roomID : '', "player_index": 1}));
|
||||
|
||||
// clear menu screen
|
||||
stopGameInputTimer();
|
||||
|
|
|
|||
70
util/gamelist/games.go
Normal file
70
util/gamelist/games.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package gamelist
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/giongto35/cloud-game/config"
|
||||
)
|
||||
|
||||
type GameInfo struct {
|
||||
Name string
|
||||
Path string
|
||||
Type string
|
||||
}
|
||||
|
||||
const gamePath = "games"
|
||||
|
||||
var GameList []GameInfo
|
||||
|
||||
func init() {
|
||||
GameList = getAllGames(gamePath)
|
||||
}
|
||||
|
||||
// GetGameInfoFromName returns game info from a gameName
|
||||
func GetGameInfoFromName(name string) GameInfo {
|
||||
for _, game := range GameList {
|
||||
if game.Name == name {
|
||||
return game
|
||||
}
|
||||
}
|
||||
|
||||
return GameInfo{}
|
||||
}
|
||||
|
||||
// getAllGames returns list of games stored in games. This call should be called when server start (package init)
|
||||
// TODO: Maybe later we need to make realtime update without server restart
|
||||
func getAllGames(gamePath string) []GameInfo {
|
||||
var games []GameInfo
|
||||
|
||||
filepath.Walk(gamePath, func(path string, info os.FileInfo, err error) error {
|
||||
if info != nil && !info.IsDir() && isValidGameType(path) {
|
||||
// Add to games list
|
||||
gameInfo := getGameInfo(path)
|
||||
games = append(games, gameInfo)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return games
|
||||
}
|
||||
|
||||
// isValidGameType check if a game is valid for cloud retro based on extension
|
||||
func isValidGameType(gamePath string) bool {
|
||||
ext := filepath.Ext(gamePath)[1:]
|
||||
_, ok := config.FileTypeToEmulator[ext]
|
||||
return ok
|
||||
}
|
||||
|
||||
// getGameInfo returns game info from a path
|
||||
func getGameInfo(path string) GameInfo {
|
||||
// Remove prefix to obtain file names
|
||||
fileName := path[len(gamePath)+1:]
|
||||
ext := filepath.Ext(fileName)
|
||||
return GameInfo{
|
||||
Name: strings.TrimSuffix(fileName, ext),
|
||||
Type: ext[1:],
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
|
@ -27,8 +27,6 @@ type Handler struct {
|
|||
rooms map[string]*room.Room
|
||||
// ID of the current server globalwise
|
||||
serverID string
|
||||
// Path to game list
|
||||
gamePath string
|
||||
// onlineStorage is client accessing to online storage (GCP)
|
||||
onlineStorage *storage.Client
|
||||
// sessions handles all sessions server is handler (key is sessionID)
|
||||
|
|
@ -36,14 +34,13 @@ type Handler struct {
|
|||
}
|
||||
|
||||
// NewHandler returns a new server
|
||||
func NewHandler(overlordHost string, gamePath string) *Handler {
|
||||
func NewHandler(overlordHost string) *Handler {
|
||||
onlineStorage := storage.NewInitClient()
|
||||
|
||||
return &Handler{
|
||||
rooms: map[string]*room.Room{},
|
||||
sessions: map[string]*Session{},
|
||||
overlordHost: overlordHost,
|
||||
gamePath: gamePath,
|
||||
onlineStorage: onlineStorage,
|
||||
}
|
||||
}
|
||||
|
|
@ -132,7 +129,7 @@ func (h *Handler) createNewRoom(gameName string, roomID string, playerIndex int)
|
|||
// or the roomID doesn't have any running sessions (room was closed)
|
||||
// we spawn a new room
|
||||
if roomID == "" || !h.isRoomRunning(roomID) {
|
||||
room := room.NewRoom(roomID, h.gamePath, gameName, h.onlineStorage)
|
||||
room := room.NewRoom(roomID, gameName, h.onlineStorage)
|
||||
// TODO: Might have race condition
|
||||
h.rooms[room.ID] = room
|
||||
return room
|
||||
|
|
|
|||
|
|
@ -76,8 +76,7 @@ func (h *Handler) RouteOverlord() {
|
|||
"start",
|
||||
func(resp cws.WSPacket) (req cws.WSPacket) {
|
||||
log.Println("Received a start request from overlord")
|
||||
session, ok := h.sessions[resp.SessionID]
|
||||
log.Println("Find ", resp.SessionID, session, ok)
|
||||
session, _ := h.sessions[resp.SessionID]
|
||||
|
||||
peerconnection := session.peerconnection
|
||||
room := h.startGameHandler(resp.Data, resp.RoomID, resp.PlayerIndex, peerconnection)
|
||||
|
|
@ -195,7 +194,7 @@ func getServerIDOfRoom(oc *OverlordClient, roomID string) string {
|
|||
}
|
||||
|
||||
func (h *Handler) startGameHandler(gameName, roomID string, playerIndex int, peerconnection *webrtc.WebRTC) *room.Room {
|
||||
log.Println("Starting game")
|
||||
log.Println("Starting game", gameName)
|
||||
// If we are connecting to overlord, request corresponding serverID based on roomID
|
||||
// TODO: check if roomID is in the current server
|
||||
room := h.getRoom(roomID)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/giongto35/cloud-game/config"
|
||||
"github.com/giongto35/cloud-game/emulator"
|
||||
"github.com/giongto35/cloud-game/libretro/nanoarch"
|
||||
"github.com/giongto35/cloud-game/util"
|
||||
"github.com/giongto35/cloud-game/util/gamelist"
|
||||
"github.com/giongto35/cloud-game/webrtc"
|
||||
storage "github.com/giongto35/cloud-game/worker/cloud-storage"
|
||||
)
|
||||
|
|
@ -48,12 +50,17 @@ type Room struct {
|
|||
}
|
||||
|
||||
// NewRoom creates a new room
|
||||
func NewRoom(roomID, gamePath, gameName string, onlineStorage *storage.Client) *Room {
|
||||
// if no roomID is given, generate it
|
||||
func NewRoom(roomID string, gameName string, onlineStorage *storage.Client) *Room {
|
||||
// If no roomID is given, generate it from gameName
|
||||
// If the is roomID, get gameName from roomID
|
||||
if roomID == "" {
|
||||
roomID = generateRoomID(gameName)
|
||||
} else {
|
||||
gameName = getGameNameFromRoomID(roomID)
|
||||
}
|
||||
log.Println("Init new room", roomID, gameName)
|
||||
gameInfo := gamelist.GetGameInfoFromName(gameName)
|
||||
|
||||
log.Println("Init new room: ", roomID, gameName, gameInfo)
|
||||
imageChannel := make(chan *image.RGBA, 30)
|
||||
audioChannel := make(chan float32, 30)
|
||||
inputChannel := make(chan int, 100)
|
||||
|
|
@ -73,7 +80,7 @@ func NewRoom(roomID, gamePath, gameName string, onlineStorage *storage.Client) *
|
|||
}
|
||||
|
||||
// Check if room is on local storage, if not, pull from GCS to local storage
|
||||
go func(gamePath, gameName, roomID string) {
|
||||
go func(game gamelist.GameInfo, roomID string) {
|
||||
// Check room is on local or fetch from server
|
||||
savepath := util.GetSavePath(roomID)
|
||||
log.Println("Check ", savepath, " on local : ", room.isGameOnLocal(savepath))
|
||||
|
|
@ -85,16 +92,11 @@ func NewRoom(roomID, gamePath, gameName string, onlineStorage *storage.Client) *
|
|||
}
|
||||
}
|
||||
|
||||
if roomID != "" {
|
||||
gameName = getGameNameFromRoomID(roomID)
|
||||
}
|
||||
log.Printf("Room %s started. GamePath: %s, GameName: %s", roomID, gamePath, gameName)
|
||||
log.Printf("Room %s started. GamePath: %s, GameName: %s", roomID, game.Path, game.Name)
|
||||
|
||||
// Spawn new emulator based on gameName and plug-in all channels
|
||||
room.director = getEmulator(gameName, roomID, imageChannel, audioChannel, inputChannel)
|
||||
path := gamePath + "/" + gameName
|
||||
meta := room.director.LoadMeta(path)
|
||||
log.Printf("Load with Meta %+v", meta)
|
||||
room.director = getEmulator(game.Type, roomID, imageChannel, audioChannel, inputChannel)
|
||||
meta := room.director.LoadMeta(game.Path)
|
||||
go room.startVideo(meta.Width, meta.Height)
|
||||
go room.startAudio(meta.AudioSampleRate)
|
||||
room.director.Start()
|
||||
|
|
@ -103,32 +105,24 @@ func NewRoom(roomID, gamePath, gameName string, onlineStorage *storage.Client) *
|
|||
|
||||
// TODO: do we need GC, we can remove it
|
||||
runtime.GC()
|
||||
}(gamePath, gameName, roomID)
|
||||
}(gameInfo, roomID)
|
||||
|
||||
return room
|
||||
}
|
||||
|
||||
// create director
|
||||
func getEmulator(gameName string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) emulator.CloudEmulator {
|
||||
gameType := getGameType(gameName)
|
||||
|
||||
switch gameType {
|
||||
case "nes":
|
||||
func getEmulator(gameType string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) emulator.CloudEmulator {
|
||||
if gameType == "nes" {
|
||||
return emulator.NewDirector(roomID, imageChannel, audioChannel, inputChannel)
|
||||
|
||||
case "gba":
|
||||
nanoarch.Init("gba", roomID, imageChannel, audioChannel, inputChannel)
|
||||
return nanoarch.NAEmulator
|
||||
|
||||
case "bin":
|
||||
nanoarch.Init("pcsx", roomID, imageChannel, audioChannel, inputChannel)
|
||||
return nanoarch.NAEmulator
|
||||
case "zip":
|
||||
nanoarch.Init("mame", roomID, imageChannel, audioChannel, inputChannel)
|
||||
return nanoarch.NAEmulator
|
||||
}
|
||||
|
||||
return nil
|
||||
ename, ok := config.FileTypeToEmulator[gameType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
nanoarch.Init(ename, roomID, imageChannel, audioChannel, inputChannel)
|
||||
return nanoarch.NAEmulator
|
||||
}
|
||||
|
||||
// getGameNameFromRoomID parse roomID to get roomID and gameName
|
||||
|
|
@ -140,19 +134,11 @@ func getGameNameFromRoomID(roomID string) string {
|
|||
return parts[1]
|
||||
}
|
||||
|
||||
func getGameType(gameName string) string {
|
||||
parts := strings.Split(gameName, ".")
|
||||
if len(parts) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
||||
// generateRoomID generate a unique room ID containing 16 digits
|
||||
func generateRoomID(gameName string) string {
|
||||
// RoomID contains random number + gameName
|
||||
// Next time when we only get roomID, we can launch game based on gameName
|
||||
roomID := strconv.FormatInt(rand.Int63(), 16) + "|" + gameName
|
||||
log.Println("Generate Room ID", roomID)
|
||||
//roomID := uuid.Must(uuid.NewV4()).String()
|
||||
return roomID
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue