Add experimental core's SRAM save/load (#273)

* Add experimental core's SRAM save/load

* Clean room.go

* Update Save RAM states

* Update dependencies

* Extract emulator states into a separate module

* Google Cloud saves don't support save RAM persistence, but save state (.dat) files only.

* Add emulator storage path into the configuration file

* Verify offline storage path
This commit is contained in:
sergystepanov 2021-02-06 01:12:24 +03:00 committed by Sergey Stepanov
parent 6584d8219c
commit 7bc9661b3d
No known key found for this signature in database
GPG key ID: A56B4929BAA8556B
16 changed files with 296 additions and 212 deletions

4
configs/config.yaml vendored
View file

@ -73,6 +73,10 @@ emulator:
width: 320
height: 240
# save directory for emulator states
# special tag {user} will be replaced with current user's home dir
storage: "{user}/.cr/save"
libretro:
cores:
paths:

15
go.mod vendored
View file

@ -13,6 +13,7 @@ require (
github.com/gofrs/flock v0.8.0
github.com/gofrs/uuid v4.0.0+incompatible
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/google/uuid v1.2.0 // indirect
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/kkyr/fig v0.2.0
@ -21,20 +22,22 @@ require (
github.com/pion/ice/v2 v2.0.15 // indirect
github.com/pion/interceptor v0.0.9
github.com/pion/rtp v1.6.2
github.com/pion/sdp/v3 v3.0.4 // indirect
github.com/pion/webrtc/v3 v3.0.3
github.com/pion/webrtc/v3 v3.0.4
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/procfs v0.3.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/veandco/go-sdl2 v0.4.5
go.opencensus.io v0.22.6 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/image v0.0.0-20201208152932-35266b937fa6
golang.org/x/mod v0.4.1 // indirect
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 // indirect
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 // indirect
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f // indirect
golang.org/x/tools v0.1.0 // indirect
google.golang.org/api v0.38.0 // indirect
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506 // indirect
google.golang.org/grpc v1.35.0 // indirect
gopkg.in/hraban/opus.v2 v2.0.0-20201025103112-d779bb1cc5a2
gopkg.in/yaml.v2 v2.4.0 // indirect

44
go.sum vendored
View file

@ -16,6 +16,7 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
cloud.google.com/go v0.66.0 h1:DZeAkuQGQqnm9Xv36SbMJEU8aFBz4wL04UpMWPWwjzg=
cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
@ -183,6 +184,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -201,6 +203,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -208,6 +211,8 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@ -369,8 +374,6 @@ github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE=
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sdp/v3 v3.0.3 h1:gJK9hk+JFD2NGIM1nXmqNCq1DkVaIZ9dlA3u3otnkaw=
github.com/pion/sdp/v3 v3.0.3/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/srtp/v2 v2.0.1 h1:kgfh65ob3EcnFYA4kUBvU/menCp9u7qaJLXwWgpobzs=
@ -390,8 +393,8 @@ github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
github.com/pion/webrtc/v3 v3.0.3 h1:nAXJ5niRFRRMneuhM56xsh3J76sv+HAHCNuBmab3YR0=
github.com/pion/webrtc/v3 v3.0.3/go.mod h1:DZonLDfkjMlsY/IGixAbcq8izHu0zJIk04DYx51KUvk=
github.com/pion/webrtc/v3 v3.0.4 h1:Tiw3H9fpfcwkvaxonB+Gv1DG9tmgYBQaM1vBagDHP40=
github.com/pion/webrtc/v3 v3.0.4/go.mod h1:1TmFSLpPYFTFXFHPtoq9eGP1ASTa9LC6FBh7sUY8cd4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -465,6 +468,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@ -488,6 +493,8 @@ go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok=
go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@ -591,10 +598,14 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@ -606,8 +617,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 h1:Lm4OryKCca1vehdsWogr9N4t7NfZxLbJoc/H0w4K4S4=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 h1:BaN3BAqnopnKjvl+15DYP6LLrbBHfbfmlFYzmFj/Q9Q=
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013 h1:55H5j7lotzuFCEOKDsMch+fRNUQ9DgtyHOUP31FNqKc=
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -617,6 +628,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -667,8 +679,10 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -740,9 +754,11 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266 h1:k7tVuG0g1JwmD3Jh8oAl1vQ
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 h1:BTs2GMGSMWpgtCpv1CE7vkJTv7XcHdcLLnAMu7UbgTY=
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
@ -773,6 +789,8 @@ google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.38.0 h1:vDyWk6eup8eQAidaZ31sNWIn8tZEL8qpbtGkBD4ytQo=
google.golang.org/api v0.38.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -820,9 +838,11 @@ google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f h1:izedQ6yVIc5mZsRuXzmSreCOlzI0lCU1HpG8yEdMiKw=
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506 h1:uLBY0yHDCj2PMQ98KWDSIDFwn9zK2zh+tgWtbvPPBjI=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=

View file

@ -12,6 +12,7 @@ type Emulator struct {
Width int
Height int
}
Storage string
Libretro LibretroConfig
}

View file

@ -2,6 +2,8 @@ package worker
import (
"encoding/json"
"log"
"strings"
"github.com/giongto35/cloud-game/v2/pkg/config"
"github.com/giongto35/cloud-game/v2/pkg/config/emulator"
@ -9,6 +11,7 @@ import (
"github.com/giongto35/cloud-game/v2/pkg/config/monitoring"
"github.com/giongto35/cloud-game/v2/pkg/config/shared"
webrtcConfig "github.com/giongto35/cloud-game/v2/pkg/config/webrtc"
"github.com/giongto35/cloud-game/v2/pkg/environment"
flag "github.com/spf13/pflag"
)
@ -35,6 +38,7 @@ func NewConfig() (conf Config) {
if err := config.LoadConfig(&conf, configPath); err == nil {
conf.Loaded = true
}
conf.expandSpecialTags()
return
}
@ -65,4 +69,21 @@ func (c *Config) Deserialize(data []byte) {
if err := json.Unmarshal(data, c); err == nil {
c.Loaded = true
}
c.expandSpecialTags()
}
// expandSpecialTags replaces all the special tags in the config.
func (c *Config) expandSpecialTags() {
// home dir
dir := c.Emulator.Storage
if dir != "" {
tag := "{user}"
if strings.Contains(dir, tag) {
userHomeDir, err := environment.GetUserHome()
if err != nil {
log.Fatalln("couldn't read user home directory", err)
}
c.Emulator.Storage = strings.Replace(dir, tag, userHomeDir, -1)
}
}
}

View file

@ -12,7 +12,6 @@ import (
config "github.com/giongto35/cloud-game/v2/pkg/config/emulator"
"github.com/giongto35/cloud-game/v2/pkg/emulator"
"github.com/giongto35/cloud-game/v2/pkg/util"
)
/*
@ -72,11 +71,22 @@ type naEmulator struct {
roomID string
gameName string
isSavingLoading bool
storage Storage
controllersMap map[string][]controllerState
done chan struct{}
}
type Storage struct {
// save path without the dir slash in the end
Path string
// contains the name of the main save file
// e.g. abc<...>293.dat
// needed for Google Cloud save/restore which
// doesn't support multiple files
MainSave string
}
// VideoExporter produces image frame to unix socket
type VideoExporter struct {
sock net.Conn
@ -103,7 +113,7 @@ const maxPort = 8
const SocketAddrTmpl = "/tmp/cloudretro-retro-%s.sock"
// NAEmulator implements CloudEmulator interface based on NanoArch(golang RetroArch)
func NewNAEmulator(roomID string, inputChannel <-chan InputEvent, conf config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
func NewNAEmulator(roomID string, inputChannel <-chan InputEvent, storage Storage, conf config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
imageChannel := make(chan GameFrame, 30)
audioChannel := make(chan []int16, 30)
@ -117,6 +127,7 @@ func NewNAEmulator(roomID string, inputChannel <-chan InputEvent, conf config.Li
HasMultitap: conf.HasMultitap,
AutoGlContext: conf.AutoGlContext,
},
storage: storage,
imageChannel: imageChannel,
audioChannel: audioChannel,
inputChannel: inputChannel,
@ -158,17 +169,17 @@ func NewVideoExporter(roomID string, imgChannel chan GameFrame) *VideoExporter {
// Init initialize new RetroArch cloud emulator
// withImageChan returns an image stream as Channel for output else it will write to unix socket
func Init(roomID string, withImageChannel bool, inputChannel <-chan InputEvent, config config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
emulator, imageChannel, audioChannel := NewNAEmulator(roomID, inputChannel, config)
func Init(roomID string, withImageChannel bool, inputChannel <-chan InputEvent, storage Storage, config config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
emu, imageChannel, audioChannel := NewNAEmulator(roomID, inputChannel, storage, config)
// Set to global NAEmulator
NAEmulator = emulator
NAEmulator = emu
if !withImageChannel {
NAEmulator.videoExporter = NewVideoExporter(roomID, imageChannel)
}
go NAEmulator.listenInput()
return emulator, imageChannel, audioChannel
return emu, imageChannel, audioChannel
}
func (na *naEmulator) listenInput() {
@ -268,9 +279,9 @@ func (na *naEmulator) ToggleMultitap() error {
return nil
}
func (na *naEmulator) GetHashPath() string {
return util.GetSavePath(na.roomID)
}
func (na *naEmulator) GetHashPath() string { return na.storage.Path + "/" + na.storage.MainSave }
func (na *naEmulator) GetSRAMPath() string { return na.storage.Path + "/" + na.roomID + ".srm" }
func (*naEmulator) GetViewport() interface{} {
return outputImg

View file

@ -2,7 +2,6 @@ package nanoarch
import (
"bufio"
"errors"
"log"
"math/rand"
"os"
@ -39,11 +38,6 @@ void bridge_retro_set_audio_sample_batch(void *f, void *callback);
bool bridge_retro_load_game(void *f, struct retro_game_info *gi);
void bridge_retro_unload_game(void *f);
void bridge_retro_run(void *f);
size_t bridge_retro_get_memory_size(void *f, unsigned id);
void* bridge_retro_get_memory_data(void *f, unsigned id);
bool bridge_retro_serialize(void *f, void *data, size_t size);
bool bridge_retro_unserialize(void *f, void *data, size_t size);
size_t bridge_retro_serialize_size(void *f);
void bridge_retro_set_controller_port_device(void *f, unsigned port, unsigned device);
bool coreEnvironment_cgo(unsigned cmd, void *data);
@ -392,25 +386,24 @@ func deinitVideo() {
video.autoGlContext = false
}
var retroHandle unsafe.Pointer
var retroInit unsafe.Pointer
var retroDeinit unsafe.Pointer
var retroAPIVersion unsafe.Pointer
var retroGetSystemInfo unsafe.Pointer
var retroGetSystemAVInfo unsafe.Pointer
var retroSetEnvironment unsafe.Pointer
var retroSetVideoRefresh unsafe.Pointer
var retroSetInputPoll unsafe.Pointer
var retroSetInputState unsafe.Pointer
var retroSetAudioSample unsafe.Pointer
var retroSetAudioSampleBatch unsafe.Pointer
var retroRun unsafe.Pointer
var retroLoadGame unsafe.Pointer
var retroUnloadGame unsafe.Pointer
var retroSerializeSize unsafe.Pointer
var retroSerialize unsafe.Pointer
var retroUnserialize unsafe.Pointer
var retroSetControllerPortDevice unsafe.Pointer
var (
retroAPIVersion unsafe.Pointer
retroDeinit unsafe.Pointer
retroGetSystemAVInfo unsafe.Pointer
retroGetSystemInfo unsafe.Pointer
retroHandle unsafe.Pointer
retroInit unsafe.Pointer
retroLoadGame unsafe.Pointer
retroRun unsafe.Pointer
retroSetAudioSample unsafe.Pointer
retroSetAudioSampleBatch unsafe.Pointer
retroSetControllerPortDevice unsafe.Pointer
retroSetEnvironment unsafe.Pointer
retroSetInputPoll unsafe.Pointer
retroSetInputState unsafe.Pointer
retroSetVideoRefresh unsafe.Pointer
retroUnloadGame unsafe.Pointer
)
func coreLoad(meta emulator.Metadata) {
isGlAllowed = meta.IsGlAllowed
@ -458,6 +451,8 @@ func coreLoad(meta emulator.Metadata) {
retroSerialize = loadFunction(retroHandle, "retro_serialize")
retroUnserialize = loadFunction(retroHandle, "retro_unserialize")
retroSetControllerPortDevice = loadFunction(retroHandle, "retro_set_controller_port_device")
retroGetMemorySize = loadFunction(retroHandle, "retro_get_memory_size")
retroGetMemoryData = loadFunction(retroHandle, "retro_get_memory_data")
mu.Unlock()
@ -593,41 +588,6 @@ func toggleMultitap() {
}
}
// serializeSize returns the amount of data the implementation requires to serialize
// internal state (save states).
// Between calls to retro_load_game() and retro_unload_game(), the
// returned size is never allowed to be larger than a previous returned
// value, to ensure that the frontend can allocate a save state buffer once.
func serializeSize() uint {
return uint(C.bridge_retro_serialize_size(retroSerializeSize))
}
// Serializes internal state and returns the state as a byte slice.
func serialize(size uint) ([]byte, error) {
data := C.malloc(C.size_t(size))
defer C.free(data)
ok := bool(C.bridge_retro_serialize(retroSerialize, data, C.size_t(size)))
if !ok {
return nil, errors.New("retro_serialize failed")
}
bytes := C.GoBytes(data, C.int(size))
return bytes, nil
}
// unserialize deserializes internal state from a byte slice.
func unserialize(bytes []byte, size uint) error {
if len(bytes) == 0 {
return nil
}
ok := bool(C.bridge_retro_unserialize(retroUnserialize, unsafe.Pointer(&bytes[0]), C.size_t(size)))
if !ok {
return errors.New("retro_unserialize failed")
}
return nil
}
func nanoarchShutdown() {
if usesLibCo {
thread.MainMaybe(func() {

View file

@ -70,12 +70,18 @@ func GetEmulatorMock(room string, system string) *EmulatorMock {
audio := make(chan []int16, 30)
inputs := make(chan InputEvent, 100)
store := Storage{
Path: os.TempDir(),
MainSave: room + ".dat",
}
// an emu
emu := &EmulatorMock{
naEmulator: naEmulator{
imageChannel: images,
audioChannel: audio,
inputChannel: inputs,
storage: store,
meta: emulator.Metadata{
LibPath: meta.Lib,
@ -129,15 +135,14 @@ func GetDefaultEmulatorMock(room string, system string, rom string) *EmulatorMoc
// The rom will be loaded from emulators' games path.
func (emu *EmulatorMock) loadRom(game string) {
fmt.Printf("%v %v\n", emu.paths.cores, emu.core)
coreLoad(emulator.Metadata{
LibPath: emu.paths.cores + emu.core,
})
coreLoad(emulator.Metadata{LibPath: emu.paths.cores + emu.core})
coreLoadGame(emu.paths.games + game)
}
// shutdownEmulator closes the emulator and cleans its resources.
func (emu *EmulatorMock) shutdownEmulator() {
_ = os.Remove(emu.GetHashPath())
_ = os.Remove(emu.GetSRAMPath())
close(emu.imageChannel)
close(emu.audioChannel)
@ -198,7 +203,7 @@ func (emu *EmulatorMock) dumpState() (string, string) {
// Locks the emulator.
func (emu *EmulatorMock) getStateHash() string {
emu.Lock()
state, _ := getState()
state, _ := getSaveState()
emu.Unlock()
return getHash(state)

View file

@ -0,0 +1,47 @@
package nanoarch
import "io/ioutil"
// Save writes the current state to the filesystem.
// Deadlock warning: locks the emulator.
func (na *naEmulator) Save() (err error) {
na.Lock()
defer na.Unlock()
if sramState := getSaveRAM(); sramState != nil {
err = toFile(na.GetSRAMPath(), sramState)
}
if saveState, err := getSaveState(); err == nil {
err = toFile(na.GetHashPath(), saveState)
}
return
}
// Load restores the state from the filesystem.
// Deadlock warning: locks the emulator.
func (na *naEmulator) Load() (err error) {
na.Lock()
defer na.Unlock()
if sramState, err := fromFile(na.GetSRAMPath()); err == nil {
restoreSaveRAM(sramState)
}
if saveState, err := fromFile(na.GetHashPath()); err == nil {
err = restoreSaveState(saveState)
}
return
}
// toFile writes the state to a file with the path.
func toFile(path string, data []byte) error {
return ioutil.WriteFile(path, data, 0644)
}
// fromFile reads the state from a file with the path.
func fromFile(path string) ([]byte, error) {
if bytes, err := ioutil.ReadFile(path); err == nil {
return bytes, nil
} else {
return []byte{}, err
}
}

View file

@ -1,60 +0,0 @@
package nanoarch
import "io/ioutil"
type state []byte
// Save writes the current state to the filesystem.
// Deadlock warning: locks the emulator.
func (na *naEmulator) Save() error {
na.Lock()
defer na.Unlock()
if state, err := getState(); err == nil {
return state.toFile(na.GetHashPath())
} else {
return err
}
}
// Load restores the state from the filesystem.
// Deadlock warning: locks the emulator.
func (na *naEmulator) Load() error {
na.Lock()
defer na.Unlock()
path := na.GetHashPath()
if state, err := fromFile(path); err == nil {
return restoreState(state)
} else {
return err
}
}
// getState returns the current emulator state.
func getState() (state, error) {
if dat, err := serialize(serializeSize()); err == nil {
return dat, nil
} else {
return state{}, err
}
}
// restoreState restores an emulator state.
func restoreState(dat state) error {
return unserialize(dat, serializeSize())
}
// toFile writes the state to a file with the path.
func (st state) toFile(path string) error {
return ioutil.WriteFile(path, st, 0644)
}
// fromFile reads the state from a file with the path.
func fromFile(path string) (state, error) {
if bytes, err := ioutil.ReadFile(path); err == nil {
return bytes, nil
} else {
return state{}, err
}
}

View file

@ -0,0 +1,100 @@
package nanoarch
/*
#include "libretro.h"
#include <stdlib.h>
size_t bridge_retro_get_memory_size(void *f, unsigned id);
void* bridge_retro_get_memory_data(void *f, unsigned id);
bool bridge_retro_serialize(void *f, void *data, size_t size);
bool bridge_retro_unserialize(void *f, void *data, size_t size);
size_t bridge_retro_serialize_size(void *f);
*/
import "C"
import (
"errors"
"unsafe"
)
// !global emulator lib state
var (
retroGetMemoryData unsafe.Pointer
retroGetMemorySize unsafe.Pointer
retroSerialize unsafe.Pointer
retroSerializeSize unsafe.Pointer
retroUnserialize unsafe.Pointer
)
// defines any memory state of the emulator
type state []byte
type mem struct {
ptr unsafe.Pointer
size uint
}
// saveStateSize returns the amount of data the implementation requires
// to serialize internal state (save states).
func saveStateSize() uint { return uint(C.bridge_retro_serialize_size(retroSerializeSize)) }
// getSaveState returns emulator internal state.
func getSaveState() (state, error) {
size := saveStateSize()
data := C.malloc(C.size_t(size))
defer C.free(data)
if !bool(C.bridge_retro_serialize(retroSerialize, data, C.size_t(size))) {
return nil, errors.New("retro_serialize failed")
}
return C.GoBytes(data, C.int(size)), nil
}
// restoreSaveState restores emulator internal state.
func restoreSaveState(st state) error {
if len(st) == 0 {
return nil
}
size := saveStateSize()
if !bool(C.bridge_retro_unserialize(retroUnserialize, unsafe.Pointer(&st[0]), C.size_t(size))) {
return errors.New("retro_unserialize failed")
}
return nil
}
// getSaveRAM returns the game save RAM (cartridge) data or a nil slice.
func getSaveRAM() state {
mem := ptSaveRAM()
if mem == nil {
return nil
}
return C.GoBytes(mem.ptr, C.int(mem.size))
}
// restoreSaveRAM restores game save RAM.
func restoreSaveRAM(st state) {
if len(st) == 0 {
return
}
if mem := ptSaveRAM(); mem != nil {
sram := (*[1 << 30]byte)(mem.ptr)[:mem.size:mem.size]
copy(sram, st)
}
}
// getMemorySize returns memory region size.
func getMemorySize(id uint) uint {
return uint(C.bridge_retro_get_memory_size(retroGetMemorySize, C.uint(id)))
}
// getMemoryData returns a pointer to memory data.
func getMemoryData(id uint) unsafe.Pointer {
return C.bridge_retro_get_memory_data(retroGetMemoryData, C.uint(id))
}
// ptSaveRam return SRAM memory pointer if core supports it or nil.
func ptSaveRAM() *mem {
ptr, size := getMemoryData(C.RETRO_MEMORY_SAVE_RAM), getMemorySize(C.RETRO_MEMORY_SAVE_RAM)
if ptr == nil || size == 0 {
return nil
}
return &mem{ptr: ptr, size: size}
}

View file

@ -1,5 +1,7 @@
package environment
import "os/user"
type Env string
const (
@ -16,3 +18,11 @@ func (env *Env) AnyOf(what ...Env) bool {
}
return false
}
func GetUserHome() (string, error) {
me, err := user.Current()
if err != nil {
return "", err
}
return me.HomeDir, nil
}

View file

@ -3,8 +3,6 @@ package util
import (
"image"
"log"
"os/user"
"unsafe"
"github.com/giongto35/cloud-game/v2/pkg/encoder"
@ -65,25 +63,6 @@ func RgbaToYuvInplace(rgba *image.RGBA, yuv []byte, width, height int) {
C.rgba2yuv(unsafe.Pointer(&yuv[0]), unsafe.Pointer(&rgba.Pix[0]), C.int(width), C.int(height), C.int(stride))
}
var homeDir string
func init() {
u, err := user.Current()
if err != nil {
log.Fatalln(err)
}
homeDir = u.HomeDir
}
// GetSavePath returns save location of game based on roomID
func GetSavePath(roomID string) string {
return savePath(roomID)
}
func savePath(hash string) string {
return homeDir + "/.cr/save/" + hash + ".dat"
}
// GetVideoEncoder returns video encoder based on some qualification.
// Actually Android is only supporting VP8 but H264 has better encoding performance
// TODO: Better use useragent attribute from frontend

View file

@ -5,7 +5,6 @@ import (
"log"
"net/url"
"os"
"path"
"time"
"github.com/giongto35/cloud-game/v2/pkg/config/worker"
@ -14,7 +13,6 @@ import (
"github.com/giongto35/cloud-game/v2/pkg/encoder"
"github.com/giongto35/cloud-game/v2/pkg/environment"
"github.com/giongto35/cloud-game/v2/pkg/games"
"github.com/giongto35/cloud-game/v2/pkg/util"
"github.com/giongto35/cloud-game/v2/pkg/webrtc"
storage "github.com/giongto35/cloud-game/v2/pkg/worker/cloud-storage"
"github.com/giongto35/cloud-game/v2/pkg/worker/room"
@ -42,7 +40,7 @@ type Handler struct {
// NewHandler returns a new server
func NewHandler(cfg worker.Config, wrk *Worker) *Handler {
// Create offline storage folder
createOfflineStorage()
createOfflineStorage(cfg.Emulator.Storage)
// Init online storage
onlineStorage := storage.NewInitClient()
@ -105,12 +103,7 @@ func setupCoordinatorConnection(host string, zone string, cfg worker.Config) (*C
scheme = "ws"
}
coordinatorURL := url.URL{
Scheme: scheme,
Host: host,
Path: "/wso",
RawQuery: "zone=" + zone,
}
coordinatorURL := url.URL{Scheme: scheme, Host: host, Path: "/wso", RawQuery: "zone=" + zone}
log.Println("Worker connecting to coordinator:", coordinatorURL.String())
conn, err := createCoordinatorConnection(&coordinatorURL)
@ -216,9 +209,10 @@ func (h *Handler) Close() {
r.Close()
}
}
func createOfflineStorage() {
dir, _ := path.Split(util.GetSavePath("dummy"))
if err := os.MkdirAll(dir, 0755); err != nil {
func createOfflineStorage(path string) {
log.Printf("Set storage: %v", path)
if err := os.MkdirAll(path, 0755); err != nil {
log.Println("Failed to create offline storage, err: ", err)
}
}

View file

@ -20,7 +20,6 @@ import (
"github.com/giongto35/cloud-game/v2/pkg/emulator/libretro/nanoarch"
"github.com/giongto35/cloud-game/v2/pkg/encoder"
"github.com/giongto35/cloud-game/v2/pkg/games"
"github.com/giongto35/cloud-game/v2/pkg/util"
"github.com/giongto35/cloud-game/v2/pkg/webrtc"
storage "github.com/giongto35/cloud-game/v2/pkg/worker/cloud-storage"
)
@ -149,10 +148,14 @@ func NewRoom(roomID string, game games.GameMetadata, videoCodec encoder.VideoCod
// Check if room is on local storage, if not, pull from GCS to local storage
go func(game games.GameMetadata, roomID string) {
store := nanoarch.Storage{
Path: cfg.Emulator.Storage,
MainSave: roomID + ".dat",
}
// Check room is on local or fetch from server
savePath := util.GetSavePath(roomID)
log.Println("Check ", savePath, " on online storage : ", room.isGameOnLocal(savePath))
if err := room.saveOnlineRoomToLocal(roomID, savePath); err != nil {
log.Printf("Check %s on online storage: %v", roomID, isGameOnLocal(store.MainSave))
if err := room.saveOnlineRoomToLocal(roomID, store.MainSave); err != nil {
log.Printf("Warn: Room %s is not in online storage, error %s", roomID, err)
}
@ -166,13 +169,13 @@ func NewRoom(roomID string, game games.GameMetadata, videoCodec encoder.VideoCod
if cfg.Encoder.WithoutGame {
// Run without game, image stream is communicated over a unix socket
imageChannel := NewVideoImporter(roomID)
director, _, audioChannel := nanoarch.Init(roomID, false, inputChannel, libretroConfig)
director, _, audioChannel := nanoarch.Init(roomID, false, inputChannel, store, libretroConfig)
room.imageChannel = imageChannel
room.director = director
room.audioChannel = audioChannel
} else {
// Run without game, image stream is communicated over image channel
director, imageChannel, audioChannel := nanoarch.Init(roomID, true, inputChannel, libretroConfig)
director, imageChannel, audioChannel := nanoarch.Init(roomID, true, inputChannel, store, libretroConfig)
room.imageChannel = imageChannel
room.director = director
room.audioChannel = audioChannel
@ -244,8 +247,8 @@ func generateRoomID(gameName string) string {
return roomID
}
func (r *Room) isGameOnLocal(savePath string) bool {
_, err := os.Open(savePath)
func isGameOnLocal(path string) bool {
_, err := os.Open(path)
return err == nil
}
@ -375,12 +378,7 @@ func (r *Room) isRoomExisted() bool {
if err == nil {
return true
}
// Check if room is in local
savePath := util.GetSavePath(r.ID)
if r.isGameOnLocal(savePath) {
return true
}
return false
return isGameOnLocal(r.ID)
}
// SaveGame will save game to local and trigger a callback to store game on onlineStorage, so the game can be accessed later
@ -402,7 +400,8 @@ func (r *Room) SaveGame() error {
return nil
}
// saveOnlineRoomToLocal save online room to local
// saveOnlineRoomToLocal save online room to local.
// !Supports only one file of main save state.
func (r *Room) saveOnlineRoomToLocal(roomID string, savePath string) error {
log.Println("Check if game is on cloud storage")
// If the game is not on local server
@ -418,21 +417,11 @@ func (r *Room) saveOnlineRoomToLocal(roomID string, savePath string) error {
return nil
}
func (r *Room) LoadGame() error {
err := r.director.LoadGame()
func (r *Room) LoadGame() error { return r.director.LoadGame() }
return err
}
func (r *Room) ToggleMultitap() error { return r.director.ToggleMultitap() }
func (r *Room) ToggleMultitap() error {
err := r.director.ToggleMultitap()
return err
}
func (r *Room) IsEmpty() bool {
return len(r.rtcSessions) == 0
}
func (r *Room) IsEmpty() bool { return len(r.rtcSessions) == 0 }
func (r *Room) IsRunningSessions() bool {
// If there is running session