refactor with callback

This commit is contained in:
giongto35 2019-04-20 01:03:19 +08:00
parent 27bdb44251
commit deff2bb735
5 changed files with 391 additions and 105 deletions

362
main.go
View file

@ -36,15 +36,9 @@ var indexFN = gameboyIndex
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 {
@ -68,6 +62,11 @@ func main() {
indexFN = debugIndex
fmt.Println("Use debug version")
}
if len(os.Args) == 3 {
if os.Args[3] == "overlord" {
IsOverlord = true
}
}
rand.Seed(time.Now().UTC().UnixNano())
fmt.Println("http://localhost:8000")
@ -164,116 +163,261 @@ func ws(w http.ResponseWriter, r *http.Request) {
}
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 {
c.SetReadDeadline(time.Now().Add(readWait))
mt, message, err := c.ReadMessage()
client := NewClient(c, webrtc.NewWebRTC())
//&Client{
//conn: c,
////wsOverlord: createOverlordClient(),
//peerconnection: webrtc.NewWebRTC(),
//}
client.syncReceive("initwebrtc", func(req WSPacket) WSPacket {
log.Println("Received user SDP")
localSession, err := client.peerconnection.StartClient(req.Data, width, height)
if err != nil {
log.Println("[!] read:", err)
break
log.Fatalln(err)
}
req := WSPacket{}
res := WSPacket{}
return WSPacket{
ID: "sdp",
Data: localSession,
}
})
err = json.Unmarshal(message, &req)
if err != nil {
log.Println("[!] json unmarshal:", err)
break
client.syncReceive("save", func(req WSPacket) (res WSPacket) {
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"
}
// 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
return res
})
client.syncReceive("load", func(req WSPacket) (res WSPacket) {
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"
}
return res
})
client.syncReceive("start", func(req WSPacket) (res WSPacket) {
gameName = req.Data
roomID = req.RoomID
playerIndex = req.PlayerIndex
//log.Println("Ping from server with game:", gameName)
//res.ID = "pong"
log.Println("Starting game")
roomID = startSession(client.peerconnection, gameName, roomID, playerIndex)
res.ID = "start"
res.RoomID = roomID
case "initwebrtc":
log.Println("Received user SDP")
localSession, err := webRTC.StartClient(req.Data, width, height)
if err != nil {
log.Fatalln(err)
}
return res
})
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":
gameName = req.Data
roomID = req.RoomID
playerIndex = req.PlayerIndex
//log.Println("Ping from server with game:", gameName)
//res.ID = "pong"
log.Println("Starting game")
roomID = startSession(webRTC, gameName, roomID, playerIndex)
res.ID = "start"
res.RoomID = roomID
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)
client.syncReceive("candidate", func(req WSPacket) (res WSPacket) {
// Unuse code
hi := pionRTC.ICECandidateInit{}
err = json.Unmarshal([]byte(req.Data), &hi)
if err != nil {
log.Println("json marshal:", err)
log.Println("[!] Cannot parse candidate: ", err)
} else {
// webRTC.AddCandidate(hi)
}
res.ID = "candidate"
c.SetWriteDeadline(time.Now().Add(writeWait))
err = c.WriteMessage(mt, []byte(stRes))
}
return res
})
client.listen()
}
//func wsold(w http.ResponseWriter, r *http.Request) {
//c, err := upgrader.Upgrade(w, r, nil)
//if err != nil {
//log.Print("[!] WS upgrade:", err)
//return
//}
//defer c.Close()
//client := &Client{
//ws: c,
//wsOverlord: createOverlordClient(),
//peerconnection: webrtc.NewWebRTC(),
//}
//if IsOverlord {
//}
//log.Println("New ws connection")
//// streaming game
//// start new games and webrtc stuff?
////isDone := false
//var gameName string
//var roomID string
//var playerIndex int
//for {
//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 "initwebrtc":
//log.Println("Received user SDP")
//localSession, err := client.peerconnection.StartClient(req.Data, width, height)
//if err != nil {
//log.Fatalln(err)
//}
//res.ID = "sdp"
//res.Data = localSession
//// Master received request Offer
////case "requestOffer":
//// Master ask the host connecting to master to request offer from browser
////case "receivedRemoteSDP":
////log.Println("Received remote rtc")
//// Relay message to overlord, so overlord will send to the correct host
//// TODO: Include room to data
////res.id = "overlordrelaysdp"
////res.Data = req.Data
////case "receivedRelaySDPFromOverlord":
////// initwebrtc
////log.Println("Received user SDP")
////localSession, err := client.peerconnection.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 "remoteStart":
//gameName = req.Data
//roomID = req.RoomID
//log.Println("Received Remote start", gameName, roomID)
//roomID = startSession(client.peerconnection, gameName, roomID, playerIndex)
//res.ID = "start"
//res.RoomID = roomID
//case "start":
//gameName = req.Data
//roomID = req.RoomID
//playerIndex = req.PlayerIndex
////log.Println("Ping from server with game:", gameName)
////res.ID = "pong"
//log.Println("Starting game")
//if overlord.isRemoteRoom(roomID) {
//res.ID = "overlordRequestOffer"
//} else {
//roomID = startSession(client.peerconnnection, gameName, roomID, playerIndex)
//}
//res.ID = "start"
//res.RoomID = roomID
//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))
//if strings.HasPrefix(res.ID, "overlord") {
//overlord.ws.WriteMessage(res.Data)
//}
//c.WriteMessage(res.Data)
//err = c.WriteMessage(mt, []byte(stRes))
//}
//}
// generateRoomID generate a unique room ID containing 16 digits
func generateRoomID() string {
roomID := strconv.FormatInt(rand.Int63(), 16)
@ -324,7 +468,7 @@ func startWebRTCSession(room *Room, webRTC *webrtc.WebRTC, playerIndex int) {
select {
case <-webRTC.Done:
fmt.Println("One session closed")
removeSession(room, webRTC)
removeSession(webRTC, room)
default:
}
// Client stopped
@ -344,19 +488,19 @@ func startWebRTCSession(room *Room, webRTC *webrtc.WebRTC, playerIndex int) {
}
}
func cleanSession(webrtc *webrtc.WebRTC) {
room, ok := rooms[webrtc.RoomID]
func cleanSession(w *webrtc.WebRTC) {
room, ok := rooms[w.RoomID]
if !ok {
return
}
removeSession(room, webrtc)
removeSession(w, room)
}
func removeSession(room *Room, webrtc *webrtc.WebRTC) {
func removeSession(w *webrtc.WebRTC, room *Room) {
room.sessionsLock.Lock()
defer room.sessionsLock.Unlock()
for i, s := range room.rtcSessions {
if s == webrtc {
if s == w {
room.rtcSessions = append(room.rtcSessions[:i], room.rtcSessions[i+1:]...)
break
}
@ -366,3 +510,13 @@ func removeSession(room *Room, webrtc *webrtc.WebRTC) {
room.Done <- struct{}{}
}
}
//func (o *Overlord) isRemoteRoom(roomID string) bool {
//err := c.WriteMessage(websocket.TextMessage, []byte(stRes))
//o.ws.WriteMessage()
//_, message, err := o.ws.ReadMessage()
//if message == "isRemoteRoom" {
//return true
//}
//return false
//}

28
overlord.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"github.com/gorilla/websocket"
)
const overlordHost = "http://localhost:9000"
type Overlord struct {
ws websocket.Conn
}
//func createOverlordClient() websocket.Conn {
//signal.Notify(interrupt, os.Interrupt)
////u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
////log.Printf("connecting to %s", u.String())
//c, _, err := websocket.DefaultDialer.Dial(overlordHost, nil)
//if err != nil {
//log.Fatal("dial:", err)
//}
//overlord := &Overlord{
//ws: c,
//}
//return overlord
//}

1
overlord/overlord.go Normal file
View file

@ -0,0 +1 @@

12
static/js/ws.js vendored
View file

@ -27,6 +27,16 @@ conn.onmessage = e => {
log("Got remote sdp");
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(d["data"]))));
break;
//case "requestOffer":
//pc.createOffer({offerToReceiveVideo: true, offerToReceiveAudio: false}).then(d => {
//pc.setLocalDescription(d).catch(log);
//})
//case "sdpremote":
//log("Got remote sdp");
//pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(d["data"]))));
//conn.send(JSON.stringify({"id": "remotestart", "data": GAME_LIST[gameIdx].nes, "room_id": roomID.value, "player_index": parseInt(playerIndex.value, 10)}));inputTimer
//break;
case "pong":
// TODO: Change name use one session
log("Recv pong. Start webrtc");
@ -51,7 +61,7 @@ conn.onmessage = e => {
function sendPing() {
// TODO: format the package with time
conn.send(JSON.stringify({"id": "pingpong", "data": "pingpong"}));
//conn.send(JSON.stringify({"id": "pingpong", "data": "pingpong"}));
}
function startWebRTC() {

93
ws.go Normal file
View file

@ -0,0 +1,93 @@
package main
import (
"encoding/json"
"log"
"time"
"github.com/giongto35/cloud-game/webrtc"
"github.com/gorilla/websocket"
)
type Client struct {
conn *websocket.Conn
wsoverlord *websocket.Conn
peerconnection *webrtc.WebRTC
// sendCallback is callback based on packetID
sendCallback map[string]func(req WSPacket)
// recvCallback is callback when receive based on ID of the packet
recvCallback map[string]func(req WSPacket)
}
type WSPacket struct {
ID string `json:"id"`
Data string `json:"data"`
RoomID string `json:"room_id"`
PlayerIndex int `json:"player_index"`
TargetHostID string `json:"target_id"`
PacketID string
}
func NewClient(conn *websocket.Conn, webrtc *webrtc.WebRTC) *Client {
sendCallback := map[string]func(WSPacket){}
recvCallback := map[string]func(WSPacket){}
return &Client{
conn: conn,
peerconnection: webrtc,
sendCallback: sendCallback,
recvCallback: recvCallback,
}
}
// syncSend sends a packet and trigger callback when the packet comes back
func (c *Client) syncSend(packet WSPacket, callback func(msg WSPacket)) {
data, err := json.Marshal(packet)
if err != nil {
return
}
c.conn.WriteMessage(0, data)
c.sendCallback[packet.PacketID] = callback
}
// syncReceive receive and response back
func (c *Client) syncReceive(id string, f func(request WSPacket) (response WSPacket)) {
c.recvCallback[id] = func(request WSPacket) {
packet := f(request)
resp, err := json.Marshal(packet)
if err != nil {
log.Println("[!] json marshal error:", err)
}
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
c.conn.WriteMessage(websocket.TextMessage, resp)
}
}
func (c *Client) listen() {
for {
_, rawMsg, err := c.conn.ReadMessage()
if err != nil {
log.Println("[!] read:", err)
break
}
wspacket := WSPacket{}
err = json.Unmarshal(rawMsg, &wspacket)
if err != nil {
continue
}
// Check if some async send is waiting for the response based on packetID
if callback, ok := c.sendCallback[wspacket.PacketID]; ok {
callback(wspacket)
delete(c.sendCallback, wspacket.PacketID)
}
// Check if some receiver with the ID is registered
if callback, ok := c.recvCallback[wspacket.ID]; ok {
callback(wspacket)
}
}
}