Merge branch 'broken_shit' into 'master'
Broken shit See merge request fainsil/booplaysgba!1
This commit is contained in:
commit
6940e6acb5
|
@ -8,5 +8,5 @@ charset = utf-8
|
|||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{yaml,yml,toml}]
|
||||
[*.{yaml,yml,toml,html,svelte}]
|
||||
indent_size = 2
|
||||
|
|
6
.env
Normal file
6
.env
Normal file
|
@ -0,0 +1,6 @@
|
|||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
WEBSOCKET_LISTEN=0.0.0.0
|
||||
WEBSOCKET_PORT=6789
|
||||
RTMP_HOST=rtmp
|
||||
RTMP_PORT=1935
|
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -6,14 +6,16 @@
|
|||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/src/server.py",
|
||||
"console": "integratedTerminal"
|
||||
"console": "integratedTerminal",
|
||||
"envFile": ""
|
||||
},
|
||||
{
|
||||
"name": "Emulator",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/src/emulator.py",
|
||||
"console": "integratedTerminal"
|
||||
"console": "integratedTerminal",
|
||||
"envFile": ""
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -13,6 +13,6 @@
|
|||
}
|
||||
},
|
||||
"python.analysis.extraPaths": [
|
||||
"./mgba/build/python/lib.linux-x86_64-3.9"
|
||||
"./mgba/src/platform/python/build/lib.linux-x86_64-3.9/mgba"
|
||||
],
|
||||
}
|
||||
|
|
51
Dockerfile
Normal file
51
Dockerfile
Normal file
|
@ -0,0 +1,51 @@
|
|||
FROM python:alpine
|
||||
|
||||
# set /code as the work directory
|
||||
WORKDIR /code
|
||||
|
||||
RUN \
|
||||
# update alpine repositories
|
||||
apk update \
|
||||
# build tools dependencies
|
||||
&& apk add build-base cmake git \
|
||||
# mgba dependencies
|
||||
&& apk add libffi-dev elfutils-dev libzip-tools minizip-dev libedit-dev sqlite-dev libepoxy-dev ffmpeg ffmpeg-dev libpng-dev jpeg-dev \
|
||||
# install poetry and cffi deps for mgba
|
||||
&& pip install poetry cffi
|
||||
|
||||
# copy poetry config files
|
||||
COPY ./pyproject.toml /code
|
||||
|
||||
RUN \
|
||||
cd /code \
|
||||
# clone mgba
|
||||
&& git clone https://github.com/mgba-emu/mgba.git mgba \
|
||||
# create build directory
|
||||
&& mkdir mgba/build \
|
||||
# go to the build directory
|
||||
&& cd mgba/build \
|
||||
# configure the build
|
||||
&& cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local .. \
|
||||
# build mGBA
|
||||
&& make \
|
||||
# install mGBA
|
||||
&& make install
|
||||
|
||||
RUN \
|
||||
cd /code/mgba/src/platform/python \
|
||||
# install mGBA bindings
|
||||
&& BINDIR=/code/mgba/build/include LIBDIR=/code/mgba/build/include python setup.py install
|
||||
|
||||
RUN \
|
||||
# go to the workdir
|
||||
cd /code/ \
|
||||
# # config poetry to not create a .venv
|
||||
&& poetry config virtualenvs.create false \
|
||||
# # upgrade pip
|
||||
&& poetry run pip install --upgrade pip \
|
||||
# install poetry
|
||||
&& poetry install --no-interaction --no-ansi --no-dev
|
||||
|
||||
# copy the src files
|
||||
COPY ./src /code/src
|
||||
COPY ./roms/pokemon.gba /code/roms/pokemon.gba
|
|
@ -35,14 +35,15 @@ Build the mGBA python bindings:
|
|||
cd booplaysgba
|
||||
mkdir mgba/build
|
||||
cd mgba/build
|
||||
cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF ..
|
||||
cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF ..
|
||||
make
|
||||
```
|
||||
|
||||
Install the dependencies :
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
poetry run pip install --upgrade pip
|
||||
BINDIR=`pwd`/mgba/build/ LIBDIR=`pwd`/mgba/build/ poetry install
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
|
41
docker-compose.yml
Normal file
41
docker-compose.yml
Normal file
|
@ -0,0 +1,41 @@
|
|||
version: "3"
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
entrypoint: poetry run python src/server.py
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- REDIS_HOST=$REDIS_HOST
|
||||
- REDIS_PORT=$REDIS_PORT
|
||||
- WEBSOCKET_LISTEN=$WEBSOCKET_LISTEN
|
||||
- WEBSOCKET_PORT=$WEBSOCKET_PORT
|
||||
ports:
|
||||
- $WEBSOCKET_PORT:$WEBSOCKET_PORT
|
||||
depends_on:
|
||||
- redis
|
||||
emulator:
|
||||
build:
|
||||
context: .
|
||||
entrypoint: poetry run python src/emulator.py
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- REDIS_HOST=$REDIS_HOST
|
||||
- REDIS_PORT=$REDIS_PORT
|
||||
- RTMP_HOST=$RTMP_HOST
|
||||
- RTMP_PORT=$RTMP_PORT
|
||||
depends_on:
|
||||
- rtmp
|
||||
- redis
|
||||
volumes:
|
||||
- ./states/:/code/states/
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- $REDIS_PORT:6379
|
||||
rtmp:
|
||||
image: tiangolo/nginx-rtmp
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- $RTMP_PORT:1935
|
48
memo.full.sh
Normal file
48
memo.full.sh
Normal file
|
@ -0,0 +1,48 @@
|
|||
0 apk update
|
||||
1 apk add build-base cmake git
|
||||
2 apk add libffi-dev elfutils-dev libzip-tools minizip-dev libedit-dev sqlite-dev libepoxy-dev ffmpeg-dev libpng-dev
|
||||
3 pip install poetry cffi
|
||||
4 mkdir /code
|
||||
5 cd /code
|
||||
6 git clone https://github.com/mgba-emu/mgba.git mgba
|
||||
7 mkdir mgba/build
|
||||
8 cd mgba/build
|
||||
9 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF ..
|
||||
10 make
|
||||
11 apk add vim
|
||||
12 vim
|
||||
13 vim pyproject.tom
|
||||
14 vim pyproject.toml
|
||||
15 cd /code
|
||||
16 poetry config virtualenvs.create false
|
||||
17 poetry run pip install --upgrade pip
|
||||
18 vim pyproject.toml
|
||||
19 poetry run pip install --upgrade pip
|
||||
20 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ poetry install --no-interaction --no-ansi --no-dev
|
||||
21 which python
|
||||
22 ls /usr/local
|
||||
23 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local
|
||||
24 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local ..
|
||||
25 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/ ..
|
||||
26 cd mgba/build
|
||||
27 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/ ..
|
||||
28 make
|
||||
29 cd /code/
|
||||
30 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ poetry install
|
||||
31 poetry install
|
||||
32 cd mgba/src/platform/python/
|
||||
33 python setup.py install
|
||||
34 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ python setup.py install
|
||||
35 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ python setup.py installfind
|
||||
36 find
|
||||
37 find / | grep flags.h
|
||||
38 BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py installfind
|
||||
39 BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py install
|
||||
40 cd ../../..
|
||||
41 cd build
|
||||
42 make install
|
||||
43 mgba
|
||||
44 cd ..
|
||||
45 cd src/platform/python/
|
||||
46 BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py install
|
||||
47 history
|
17
memo.sh
Normal file
17
memo.sh
Normal file
|
@ -0,0 +1,17 @@
|
|||
apk update
|
||||
apk add build-base cmake git vim
|
||||
apk add libffi-dev elfutils-dev libzip-tools minizip-dev libedit-dev sqlite-dev libepoxy-dev ffmpeg-dev libpng-dev jpeg-dev
|
||||
pip install poetry cffi
|
||||
mkdir /code
|
||||
cd /code
|
||||
vim pyproject.toml # copy le pyproject.tom manuel
|
||||
git clone https://github.com/mgba-emu/mgba.git mgba
|
||||
mkdir mgba/build
|
||||
cd mgba/build
|
||||
cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/ ..
|
||||
make
|
||||
make install
|
||||
cd /code/mgba/src/platform/python/
|
||||
BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py install
|
||||
cd /code/
|
||||
poetry install
|
42
poetry.lock
generated
42
poetry.lock
generated
|
@ -176,7 +176,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\"
|
|||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.3.3"
|
||||
version = "2.3.4"
|
||||
description = "File identification library for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -207,6 +207,27 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "mgba"
|
||||
version = "0.10.0.7138+g59cb5c189"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
develop = false
|
||||
|
||||
[package.dependencies]
|
||||
cached-property = "*"
|
||||
cffi = ">=1.6"
|
||||
|
||||
[package.extras]
|
||||
cinema = ["pytest"]
|
||||
pil = ["Pillow (>=2.3)"]
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
url = "mgba/src/platform/python"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.910"
|
||||
|
@ -250,7 +271,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
|||
|
||||
[[package]]
|
||||
name = "pbr"
|
||||
version = "5.6.0"
|
||||
version = "5.7.0"
|
||||
description = "Python Build Reasonableness"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -302,7 +323,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.20"
|
||||
version = "2.21"
|
||||
description = "C parser in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -454,7 +475,7 @@ python-versions = ">=3.7"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "23107f337b52d511f360f86746a69cbeec745e69d23beb1051a2fb9c5c303fd9"
|
||||
content-hash = "06c8f160e3bf91f78480ff21a5e205147f1a23c38a405232ab8252e36cc7809f"
|
||||
|
||||
[metadata.files]
|
||||
asyncio = [
|
||||
|
@ -568,8 +589,8 @@ gitpython = [
|
|||
{file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"},
|
||||
]
|
||||
identify = [
|
||||
{file = "identify-2.3.3-py2.py3-none-any.whl", hash = "sha256:ffab539d9121b386ffdea84628ff3eefda15f520f392ce11b393b0a909632cdf"},
|
||||
{file = "identify-2.3.3.tar.gz", hash = "sha256:b9ffbeb7ed87e96ce017c66b80ca04fda3adbceb5c74e54fc7d99281d27d0859"},
|
||||
{file = "identify-2.3.4-py2.py3-none-any.whl", hash = "sha256:4de55a93e0ba72bf917c840b3794eb1055a67272a1732351c557c88ec42011b1"},
|
||||
{file = "identify-2.3.4.tar.gz", hash = "sha256:595283a1c3a078ac5774ad4dc4d1bdd0c1602f60bcf11ae673b64cb2b1945762"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-5.10.0-py3-none-any.whl", hash = "sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f"},
|
||||
|
@ -579,6 +600,7 @@ mccabe = [
|
|||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
mgba = []
|
||||
mypy = [
|
||||
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
||||
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
||||
|
@ -617,8 +639,8 @@ pathspec = [
|
|||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
pbr = [
|
||||
{file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"},
|
||||
{file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"},
|
||||
{file = "pbr-5.7.0-py2.py3-none-any.whl", hash = "sha256:60002958e459b195e8dbe61bf22bcf344eedf1b4e03a321a5414feb15566100c"},
|
||||
{file = "pbr-5.7.0.tar.gz", hash = "sha256:4651ca1445e80f2781827305de3d76b3ce53195f2227762684eb08f17bc473b7"},
|
||||
]
|
||||
pillow = [
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
||||
|
@ -676,8 +698,8 @@ pycodestyle = [
|
|||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
||||
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
pydocstyle = [
|
||||
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
|
||||
|
|
|
@ -4,9 +4,7 @@ version = "0.1.0"
|
|||
description = "Émulateur collaboratif pour patienter dans le B00"
|
||||
authors = ["Laureηt <laurentfainsin@protonmail.com>"]
|
||||
license = "MIT"
|
||||
packages = [
|
||||
{ include = "mgba", from = "mgba/build/python/lib.linux-x86_64-3.9" }
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
asyncio = "^3.4.3"
|
||||
|
@ -15,6 +13,7 @@ cffi = "^1.15.0"
|
|||
cached-property = "^1.5.2"
|
||||
Pillow = "^8.4.0"
|
||||
redis = "^3.5.3"
|
||||
mgba = { path="mgba/src/platform/python" }
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^21.9b0"
|
||||
|
|
89
src/Controller.svelte
Normal file
89
src/Controller.svelte
Normal file
|
@ -0,0 +1,89 @@
|
|||
<script lang="ts">
|
||||
const websocket: WebSocket = new WebSocket("ws://localhost:6789/");
|
||||
|
||||
const sendAction = (key: string) => () => {
|
||||
websocket.send(JSON.stringify({ action: key }));
|
||||
};
|
||||
</script>
|
||||
|
||||
<table class="buttons">
|
||||
<tr>
|
||||
<td colspan="2" style="padding-bottom: 1rem;">
|
||||
<button id="l" on:click={sendAction("l")}>L</button>
|
||||
</td>
|
||||
<td />
|
||||
<td colspan="2" style="padding-bottom: 1rem; text-align: right;">
|
||||
<button id="r" on:click={sendAction("r")}>R</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td />
|
||||
<td>
|
||||
<button id="up" on:click={sendAction("up")}> ∧ </button>
|
||||
</td>
|
||||
<td />
|
||||
<td>
|
||||
<button id="select" on:click={sendAction("select")}> select </button>
|
||||
</td>
|
||||
<td>
|
||||
<button id="start" on:click={sendAction("start")}> start </button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<button id="left" on:click={sendAction("left")}> < </button>
|
||||
</td>
|
||||
<td />
|
||||
<td style="padding-right: 3rem;">
|
||||
<button id="right" on:click={sendAction("right")}> > </button>
|
||||
</td>
|
||||
<td />
|
||||
<td />
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td />
|
||||
<td>
|
||||
<button id="down" on:click={sendAction("down")}> ∨ </button>
|
||||
</td>
|
||||
<td />
|
||||
<td>
|
||||
<button id="a" on:click={sendAction("a")}>A</button>
|
||||
</td>
|
||||
<td>
|
||||
<button id="b" on:click={sendAction("b")}>B</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<style>
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#a,
|
||||
#b {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#left,
|
||||
#right,
|
||||
#up,
|
||||
#down {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
#start,
|
||||
#select,
|
||||
#l,
|
||||
#r {
|
||||
width: 3.5rem;
|
||||
border-radius: 25%;
|
||||
}
|
||||
</style>
|
114
src/admin.html
114
src/admin.html
|
@ -2,78 +2,78 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Admin</title>
|
||||
<title>Admin</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#dashboard {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
#dashboard {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="login">
|
||||
<input type="password" id="password-text">
|
||||
<input type="button" id="password-button" value="Login" onclick="sendCreds();">
|
||||
</div>
|
||||
<div id="login">
|
||||
<input type="password" id="password-text">
|
||||
<input type="button" id="password-button" value="Login" onclick="sendCreds();">
|
||||
</div>
|
||||
|
||||
<div id="dashboard">
|
||||
<ul id="stateList"></ul>
|
||||
<button id="save">save</button>
|
||||
</div>
|
||||
<div id="dashboard">
|
||||
<ul id="stateList"></ul>
|
||||
<button id="save">save</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
||||
var passwordInput = document.querySelector('#password-text');
|
||||
<script>
|
||||
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
||||
var passwordInput = document.querySelector('#password-text');
|
||||
|
||||
var divLogin = document.querySelector("#login");
|
||||
var divDashboard = document.querySelector("#dashboard");
|
||||
var stateList = document.querySelector("#stateList");
|
||||
var saveButton = document.getElementById('save');
|
||||
var divLogin = document.querySelector("#login");
|
||||
var divDashboard = document.querySelector("#dashboard");
|
||||
var stateList = document.querySelector("#stateList");
|
||||
var saveButton = document.getElementById('save');
|
||||
|
||||
saveButton.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ 'admin': 'save' }));
|
||||
}
|
||||
saveButton.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ 'admin': 'save' }));
|
||||
}
|
||||
|
||||
function receiveStates(ev) {
|
||||
let msg = JSON.parse(ev.data);
|
||||
let states = msg.state
|
||||
for (var i = 0; i < states.length; i++) {
|
||||
var state = states[i];
|
||||
var li = document.createElement('li');
|
||||
li.appendChild(document.createTextNode(state));
|
||||
stateList.appendChild(li);
|
||||
}
|
||||
}
|
||||
function receiveStates(ev) {
|
||||
let msg = JSON.parse(ev.data);
|
||||
let states = msg.state
|
||||
for (var i = 0; i < states.length; i++) {
|
||||
var state = states[i];
|
||||
var li = document.createElement('li');
|
||||
li.appendChild(document.createTextNode(state));
|
||||
stateList.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
function authSuccess(ev) {
|
||||
let msg = JSON.parse(ev.data);
|
||||
if (msg.auth === "success") {
|
||||
divLogin.style.display = "none";
|
||||
divDashboard.style.display = "unset";
|
||||
websocket.removeEventListener('message', authSuccess);
|
||||
websocket.send(JSON.stringify({ "state": "get" }));
|
||||
websocket.addEventListener('message', receiveStates);
|
||||
}
|
||||
};
|
||||
function authSuccess(ev) {
|
||||
let msg = JSON.parse(ev.data);
|
||||
if (msg.auth === "success") {
|
||||
divLogin.style.display = "none";
|
||||
divDashboard.style.display = "unset";
|
||||
websocket.removeEventListener('message', authSuccess);
|
||||
websocket.send(JSON.stringify({ "state": "get" }));
|
||||
websocket.addEventListener('message', receiveStates);
|
||||
}
|
||||
};
|
||||
|
||||
function sendCreds() {
|
||||
var message = JSON.stringify({ "auth": passwordInput.value });
|
||||
websocket.send(message)
|
||||
websocket.addEventListener('message', authSuccess);
|
||||
};
|
||||
function sendCreds() {
|
||||
var message = JSON.stringify({ "auth": passwordInput.value });
|
||||
websocket.send(message)
|
||||
websocket.addEventListener('message', authSuccess);
|
||||
};
|
||||
|
||||
passwordInput.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") { sendCreds(); }
|
||||
});
|
||||
</script>
|
||||
passwordInput.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") { sendCreds(); }
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
63
src/arrow.svg
Normal file
63
src/arrow.svg
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="8.0677929mm"
|
||||
height="9.2203341mm"
|
||||
viewBox="0 0 8.0677928 9.2203341"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
sodipodi:docname="arrow.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="13.108739"
|
||||
inkscape:cx="6.5986514"
|
||||
inkscape:cy="-6.7512214"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1054"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-93.682808,-119.4171)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="91.903885"
|
||||
y="129.03831"
|
||||
id="text49897"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan49895"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.6389px;font-family:Inter;-inkscape-font-specification:'Inter, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583"
|
||||
x="91.903885"
|
||||
y="129.03831">></tspan></text>
|
||||
<g
|
||||
aria-label=">"
|
||||
id="text51577"
|
||||
style="font-size:10.5833px;line-height:1.25;stroke-width:0.264583">
|
||||
<path
|
||||
d="m 101.7506,124.52837 -8.067792,4.10906 v -1.60354 l 6.188649,-2.98157 -0.05011,0.10022 v -0.25055 l 0.05011,0.10022 -6.188649,-2.98158 v -1.60353 l 8.067792,4.10906 z"
|
||||
style="font-size:17.6389px;font-family:Inter;-inkscape-font-specification:'Inter, Normal'"
|
||||
id="path51587" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
216
src/client.html
216
src/client.html
|
@ -2,127 +2,127 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Telecommande</title>
|
||||
<title>Telecommande</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: auto;
|
||||
margin-top: 30vh;
|
||||
}
|
||||
table {
|
||||
margin: auto;
|
||||
margin-top: 30vh;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#a,
|
||||
#b {
|
||||
border-radius: 50%;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
#a,
|
||||
#b {
|
||||
border-radius: 50%;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#left,
|
||||
#right,
|
||||
#up,
|
||||
#down {
|
||||
border-radius: 10%;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
#left,
|
||||
#right,
|
||||
#up,
|
||||
#down {
|
||||
border-radius: 10%;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#start,
|
||||
#select,
|
||||
#l,
|
||||
#r {
|
||||
border-radius: 25%;
|
||||
width: 3.5rem;
|
||||
}
|
||||
</style>
|
||||
#start,
|
||||
#select,
|
||||
#l,
|
||||
#r {
|
||||
border-radius: 25%;
|
||||
width: 3.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<table class="buttons">
|
||||
<tr>
|
||||
<td colspan="2" style="padding-bottom: 1rem;"><button id="l">L</button></td>
|
||||
<td></td>
|
||||
<td colspan="2" style="padding-bottom: 1rem; text-align: right;"><button id="r">R</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button id="up"> ∧ </button></td>
|
||||
<td></td>
|
||||
<td><button id="select">select</button></td>
|
||||
<td><button id="start">start</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><button id="left">
|
||||
< </button>
|
||||
</td>
|
||||
<td></td>
|
||||
<td style="padding-right: 3rem;"><button id="right"> > </button></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button id="down"> ∨ </button></td>
|
||||
<td></td>
|
||||
<td><button id="a">A</button></td>
|
||||
<td><button id="b">B</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="buttons">
|
||||
<tr>
|
||||
<td colspan="2" style="padding-bottom: 1rem;"><button id="l">L</button></td>
|
||||
<td></td>
|
||||
<td colspan="2" style="padding-bottom: 1rem; text-align: right;"><button id="r">R</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button id="up"> ∧ </button></td>
|
||||
<td></td>
|
||||
<td><button id="select">select</button></td>
|
||||
<td><button id="start">start</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><button id="left">
|
||||
< </button>
|
||||
</td>
|
||||
<td></td>
|
||||
<td style="padding-right: 3rem;"><button id="right"> > </button></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button id="down"> ∨ </button></td>
|
||||
<td></td>
|
||||
<td><button id="a">A</button></td>
|
||||
<td><button id="b">B</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
||||
<script>
|
||||
var websocket = new WebSocket("ws://localhost:6789/");
|
||||
|
||||
var a = document.getElementById('a'),
|
||||
b = document.getElementById('b'),
|
||||
select = document.getElementById('select'),
|
||||
start = document.getElementById('start'),
|
||||
right = document.getElementById('right'),
|
||||
left = document.getElementById('left'),
|
||||
up = document.getElementById('up'),
|
||||
down = document.getElementById('down'),
|
||||
r = document.getElementById('r'),
|
||||
l = document.getElementById('l');
|
||||
var a = document.getElementById('a'),
|
||||
b = document.getElementById('b'),
|
||||
select = document.getElementById('select'),
|
||||
start = document.getElementById('start'),
|
||||
right = document.getElementById('right'),
|
||||
left = document.getElementById('left'),
|
||||
up = document.getElementById('up'),
|
||||
down = document.getElementById('down'),
|
||||
r = document.getElementById('r'),
|
||||
l = document.getElementById('l');
|
||||
|
||||
a.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'a' }));
|
||||
}
|
||||
b.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'b' }));
|
||||
}
|
||||
select.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'select' }));
|
||||
}
|
||||
start.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'start' }));
|
||||
}
|
||||
right.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'right' }));
|
||||
}
|
||||
left.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'left' }));
|
||||
}
|
||||
up.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'up' }));
|
||||
}
|
||||
down.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'down' }));
|
||||
}
|
||||
r.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'r' }));
|
||||
}
|
||||
l.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'l' }));
|
||||
}
|
||||
</script>
|
||||
a.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'a' }));
|
||||
}
|
||||
b.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'b' }));
|
||||
}
|
||||
select.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'select' }));
|
||||
}
|
||||
start.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'start' }));
|
||||
}
|
||||
right.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'right' }));
|
||||
}
|
||||
left.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'left' }));
|
||||
}
|
||||
up.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'up' }));
|
||||
}
|
||||
down.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'down' }));
|
||||
}
|
||||
r.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'r' }));
|
||||
}
|
||||
l.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ action: 'l' }));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
105
src/emulator.py
105
src/emulator.py
|
@ -1,4 +1,6 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from subprocess import PIPE, Popen # nosec
|
||||
|
||||
|
@ -7,17 +9,36 @@ import mgba.image
|
|||
import mgba.log
|
||||
import redis
|
||||
|
||||
from settings import FPS, HEIGHT, KEYS, MGBA_KEYS, POLLING_RATE, REDIS_INIT, SPF, WIDTH
|
||||
from settings import (
|
||||
EMULATOR_FPS,
|
||||
EMULATOR_HEIGHT,
|
||||
EMULATOR_POLLING_RATE,
|
||||
EMULATOR_ROM_PATH,
|
||||
EMULATOR_SPF,
|
||||
EMULATOR_WIDTH,
|
||||
FFMPEG_BITRATE,
|
||||
FFMPEG_FPS,
|
||||
FFMPEG_HEIGHT,
|
||||
FFMPEG_WIDTH,
|
||||
KEYS_ID,
|
||||
KEYS_MGBA,
|
||||
KEYS_RESET,
|
||||
REDIS_HOST,
|
||||
REDIS_PORT,
|
||||
RTMP_STREAM_URI,
|
||||
)
|
||||
from utils import States
|
||||
|
||||
core = mgba.core.load_path("roms/pokemon.gba")
|
||||
# core = mgba.core.load_path("roms/BtnTest.gba")
|
||||
screen = mgba.image.Image(WIDTH, HEIGHT)
|
||||
core = mgba.core.load_path(EMULATOR_ROM_PATH)
|
||||
screen = mgba.image.Image(EMULATOR_WIDTH, EMULATOR_HEIGHT)
|
||||
core.set_video_buffer(screen)
|
||||
core.reset()
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
mgba.log.silence()
|
||||
r = redis.Redis(host="localhost", port=6379, db=0)
|
||||
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
|
||||
|
||||
states: States = States()
|
||||
|
||||
|
||||
def next_action():
|
||||
|
@ -26,9 +47,9 @@ def next_action():
|
|||
Returns:
|
||||
int: key used by mgba
|
||||
"""
|
||||
votes = list(map(int, r.mget(KEYS)))
|
||||
votes: list[int] = list(map(int, r.mget(KEYS_ID)))
|
||||
if any(votes):
|
||||
r.mset(REDIS_INIT)
|
||||
r.mset(KEYS_RESET)
|
||||
return votes.index(max(votes))
|
||||
else:
|
||||
return -1
|
||||
|
@ -43,45 +64,77 @@ stream = Popen(
|
|||
"-vcodec",
|
||||
"png",
|
||||
"-r",
|
||||
f"{FPS}",
|
||||
f"{EMULATOR_FPS}",
|
||||
"-s",
|
||||
f"{WIDTH}x{HEIGHT}",
|
||||
f"{EMULATOR_WIDTH}x{EMULATOR_HEIGHT}",
|
||||
"-i",
|
||||
"-",
|
||||
"-f",
|
||||
"flv",
|
||||
"-s",
|
||||
f"{WIDTH}x{HEIGHT}",
|
||||
f"{FFMPEG_WIDTH}x{FFMPEG_HEIGHT}",
|
||||
"-r",
|
||||
"30",
|
||||
f"{FFMPEG_FPS}",
|
||||
"-b:v",
|
||||
"2M",
|
||||
FFMPEG_BITRATE,
|
||||
"-fflags",
|
||||
"nobuffer",
|
||||
"-flags",
|
||||
"low_delay",
|
||||
"-strict",
|
||||
"experimental",
|
||||
"rtmp://localhost:1935/live/test",
|
||||
# "-loglevel",
|
||||
# "quiet",
|
||||
RTMP_STREAM_URI,
|
||||
],
|
||||
stdin=PIPE,
|
||||
)
|
||||
|
||||
while True:
|
||||
|
||||
last_frame_t = time.time()
|
||||
def state_manager(loop):
|
||||
print("ici")
|
||||
ps = r.pubsub()
|
||||
ps.subscribe("admin")
|
||||
while True:
|
||||
for message in ps.listen():
|
||||
logging.debug(message)
|
||||
if message["type"] == "message":
|
||||
data = message["data"].decode("utf-8")
|
||||
if data == "save":
|
||||
asyncio.ensure_future(states.save(core), loop=loop)
|
||||
elif data.startswith("load:"):
|
||||
asyncio.ensure_future(states.load(core, data.removeprefix("load:")), loop=loop)
|
||||
|
||||
if not (core.frame_counter % POLLING_RATE):
|
||||
core.clear_keys(*MGBA_KEYS)
|
||||
next_key = next_action()
|
||||
if next_key != -1:
|
||||
core.set_keys(next_key)
|
||||
|
||||
core.run_frame()
|
||||
async def emulator():
|
||||
while True:
|
||||
last_frame_t = time.time()
|
||||
|
||||
image = screen.to_pil().convert("RGB")
|
||||
image.save(stream.stdin, "PNG")
|
||||
if not (core.frame_counter % EMULATOR_POLLING_RATE):
|
||||
core.clear_keys(*KEYS_MGBA)
|
||||
next_key = next_action()
|
||||
if next_key != -1:
|
||||
core.set_keys(next_key)
|
||||
|
||||
sleep_t = last_frame_t - time.time() + SPF
|
||||
if sleep_t > 0:
|
||||
time.sleep(sleep_t)
|
||||
core.run_frame()
|
||||
|
||||
image = screen.to_pil().convert("RGB")
|
||||
image.save(stream.stdin, "PNG")
|
||||
|
||||
sleep_t = last_frame_t - time.time() + EMULATOR_SPF
|
||||
if sleep_t > 0:
|
||||
await asyncio.sleep(sleep_t)
|
||||
|
||||
|
||||
async def main(loop):
|
||||
thread = threading.Thread(target=state_manager, args=(loop,))
|
||||
thread.start()
|
||||
|
||||
task_emulator = loop.create_task(emulator())
|
||||
await task_emulator
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main(loop))
|
||||
loop.close()
|
||||
|
|
|
@ -6,13 +6,22 @@ import time
|
|||
import redis
|
||||
import websockets
|
||||
|
||||
from settings import KEYS, PASSWORD_ADMIN, REDIS_INIT, USER_TIMEOUT
|
||||
from settings import (
|
||||
KEYS_ID,
|
||||
KEYS_RESET,
|
||||
PASSWORD_ADMIN,
|
||||
REDIS_HOST,
|
||||
REDIS_PORT,
|
||||
USER_TIMEOUT,
|
||||
WEBSOCKET_LISTEN,
|
||||
WEBSOCKET_PORT,
|
||||
)
|
||||
from utils import User, Users
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
r = redis.Redis(host="localhost", port=6379, db=0)
|
||||
r.mset(REDIS_INIT)
|
||||
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
|
||||
r.mset(KEYS_RESET)
|
||||
|
||||
USERS: Users = Users()
|
||||
|
||||
|
@ -31,16 +40,27 @@ async def parse_message(user: User, message: dict[str, str]) -> None:
|
|||
logging.debug(f"admin authenticated: {user}")
|
||||
await user.send('{"auth":"success"}')
|
||||
|
||||
if "admin" in message:
|
||||
if user == USERS.admin:
|
||||
data = message["admin"]
|
||||
if data == "save":
|
||||
r.publish("admin", "save")
|
||||
elif data == data.startswith("load:"):
|
||||
r.publish("admin", data)
|
||||
else:
|
||||
logging.error(f"unsupported admin action: {data}")
|
||||
else:
|
||||
logging.error(f"user is not admin: {user}")
|
||||
|
||||
if "action" in message:
|
||||
data = message["action"]
|
||||
|
||||
if user.last_message + USER_TIMEOUT > time.time():
|
||||
logging.debug(f"dropping action: {data}")
|
||||
return None
|
||||
elif data in KEYS:
|
||||
elif data in KEYS_ID:
|
||||
r.incr(data)
|
||||
user.last_message = time.time()
|
||||
user.has_voted = True
|
||||
else:
|
||||
logging.error(f"unsupported action: {data}")
|
||||
|
||||
|
@ -67,7 +87,7 @@ async def handler(websocket, path: str):
|
|||
|
||||
async def main():
|
||||
"""Start the websocket server."""
|
||||
async with websockets.serve(handler, "localhost", 6789):
|
||||
async with websockets.serve(handler, WEBSOCKET_LISTEN, WEBSOCKET_PORT): # nosec
|
||||
await asyncio.Future() # run forever
|
||||
|
||||
|
||||
|
|
|
@ -1,14 +1,36 @@
|
|||
WIDTH: int = 240
|
||||
HEIGHT: int = 160
|
||||
from os import getenv
|
||||
|
||||
URI: str = "ws://127.0.0.1:6789/"
|
||||
PASSWORD_ADMIN: str = "password_admin"
|
||||
WEBSOCKET_HOST: str = getenv("WEBSOCKET_HOST", "localhost")
|
||||
WEBSOCKET_PORT: int = int(getenv("WEBSOCKET_PORT", 6789))
|
||||
WEBSOCKET_URI: str = f"ws://{WEBSOCKET_HOST}:{WEBSOCKET_PORT}/"
|
||||
WEBSOCKET_LISTEN: str = getenv("WEBSOCKET_LISTEN", "localhost")
|
||||
|
||||
FPS: int = 60
|
||||
SPF: float = 1.0 / FPS
|
||||
HZ: int = 10
|
||||
POLLING_RATE: int = FPS // HZ
|
||||
USER_TIMEOUT: float = 0.5
|
||||
RTMP_HOST: str = getenv("RTMP_HOST", "localhost")
|
||||
RTMP_PORT: int = int(getenv("RTMP_PORT", 1935))
|
||||
RTMP_URI: str = f"rtmp://{RTMP_HOST}:{RTMP_PORT}/"
|
||||
RTMP_STREAM_PATH: str = getenv("RTMP_STREAM_PATH", "live")
|
||||
RTMP_STREAM_KEY: str = getenv("RTMP_STREAM_KEY", "test")
|
||||
RTMP_STREAM_URI: str = RTMP_URI + f"{RTMP_STREAM_PATH}/{RTMP_STREAM_KEY}"
|
||||
|
||||
REDIS_HOST: str = getenv("REDIS_HOST", "localhost")
|
||||
REDIS_PORT: int = int(getenv("REDIS_PORT", 6379))
|
||||
|
||||
EMULATOR_WIDTH: int = int(getenv("EMULATOR_WIDTH", 240))
|
||||
EMULATOR_HEIGHT: int = int(getenv("EMULATOR_HEIGHT", 160))
|
||||
EMULATOR_FPS: int = int(getenv("EMULATOR_FPS", 60))
|
||||
EMULATOR_SPF: float = 1.0 / EMULATOR_FPS
|
||||
EMULATOR_INPUT_HZ: int = int(getenv("EMULATOR_INPUT_HZ", 10))
|
||||
EMULATOR_POLLING_RATE: int = EMULATOR_FPS // EMULATOR_INPUT_HZ
|
||||
EMULATOR_ROM_PATH: str = getenv("EMULATOR_ROM_PATH", "roms/pokemon.gba")
|
||||
|
||||
FFMPEG_WIDTH: int = int(getenv("FFMPEG_WIDTH", EMULATOR_WIDTH))
|
||||
FFMPEG_HEIGHT: int = int(getenv("FFMPEG_HEIGHT", EMULATOR_HEIGHT))
|
||||
FFMPEG_FPS: int = int(getenv("FFMPEG_FPS", 30))
|
||||
FFMPEG_BITRATE: str = getenv("FFMPEG_BIRATE", "2M")
|
||||
|
||||
PASSWORD_ADMIN: str = getenv("PASSWORD_ADMIN", "password_admin")
|
||||
|
||||
USER_TIMEOUT: float = float(getenv("USER_TIMEOUT", 0.5))
|
||||
|
||||
KEYMAP: dict[str, int] = {
|
||||
"a": 0,
|
||||
|
@ -22,6 +44,6 @@ KEYMAP: dict[str, int] = {
|
|||
"r": 8,
|
||||
"l": 9,
|
||||
}
|
||||
KEYS: tuple = tuple(KEYMAP.keys())
|
||||
MGBA_KEYS: tuple = tuple(KEYMAP.values())
|
||||
REDIS_INIT: dict = dict([(x, 0) for x in KEYS])
|
||||
KEYS_ID: tuple = tuple(KEYMAP.keys())
|
||||
KEYS_MGBA: tuple = tuple(KEYMAP.values())
|
||||
KEYS_RESET: dict = dict([(x, 0) for x in KEYS_ID])
|
||||
|
|
33
src/utils.py
33
src/utils.py
|
@ -1,15 +1,17 @@
|
|||
import logging
|
||||
import os
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
|
||||
from mgba._pylib import ffi
|
||||
|
||||
|
||||
class User:
|
||||
"""Store infos related to a connected user."""
|
||||
|
||||
websocket: Any
|
||||
last_message: float
|
||||
has_voted: bool
|
||||
|
||||
def __init__(self, websocket: Any) -> None:
|
||||
"""Construct a User object.
|
||||
|
@ -19,7 +21,6 @@ class User:
|
|||
"""
|
||||
self.websocket = websocket
|
||||
self.last_message = time.time()
|
||||
self.has_voted = False
|
||||
|
||||
async def send(self, data: str):
|
||||
"""Send data through the user's websocket.
|
||||
|
@ -42,7 +43,6 @@ class User:
|
|||
class Users(set):
|
||||
"""Store `User`s connected to the server."""
|
||||
|
||||
emulator: Optional[User] = None
|
||||
admin: Optional[User] = None
|
||||
|
||||
def register(self, user: User):
|
||||
|
@ -63,7 +63,26 @@ class Users(set):
|
|||
self.remove(user)
|
||||
logging.debug(f"user unregistered: {self}")
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear the `has_voted` of each user in the set."""
|
||||
for user in self:
|
||||
user.has_voted = False
|
||||
|
||||
class States(set):
|
||||
"""Save and load states from files."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Construct a `States` object."""
|
||||
files = os.listdir("states")
|
||||
states = list(filter(lambda x: x.endswith(".state"), files))
|
||||
self.update(states)
|
||||
|
||||
async def save(self, core):
|
||||
state = core.save_raw_state()
|
||||
with open(f"states/{time.strftime('%Y-%m-%dT%H:%M:%S')}.state", "wb") as state_file:
|
||||
for byte in state:
|
||||
state_file.write(byte.to_bytes(4, byteorder="big", signed=False))
|
||||
self.add(state)
|
||||
|
||||
async def load(self, core, filename):
|
||||
state = ffi.new("unsigned char[397312]") # pulled 397312 from my ass
|
||||
with open(f"states/{filename}.state", "rb") as state_file:
|
||||
for i in range(len(state)):
|
||||
state[i] = int.from_bytes(state_file.read(4), byteorder="big", signed=False)
|
||||
core.load_raw_state(state)
|
||||
|
|
Loading…
Reference in a new issue