From eac32ae083298f4d6bf2d359001109259233778a Mon Sep 17 00:00:00 2001 From: RodrigoDornelles Date: Fri, 4 Oct 2024 18:09:57 -0300 Subject: [PATCH] feat: revision 0.0.7 (#89) * change: wasmoon decoupling in core html5 * docs: add readme for npmjs @gamely/core-native-html5 * change: organize npmjs packages * feat: add module i18n #85 * docs: craete a new doxygen filter * feat: native_draw_text html5 #83 * feat: add GC and Images #43 #45 #84 * docs: improve doxygen * ci: publish all examples in matrix on web * ci: fix deploy in cloudflare when is workflow_dispatch * fix: std.draw.font in love * feat: i18n uses system language as default * feat: add support to haxe language #86 * docs: improve haxe docs and move command * fix: tool-haxe-build missing close file * fix: print better error in tool-haxe-build * fix: zeebo_module nil * feat: add module hash djb2 and fingerprint * docs: @hideparam in doxygen filter * feat: add support to javascript * feat: support custom engine in core-native-html5 * docs: add @renamefunc to doxygen filter * style: module renames #32 * fix: memory leak in std.draw.poly * feat: add subcommand tool-love-zip #37 * ci: optmize tools/cd_npm_core-native-html5.lua * style: fixes luau linter --- .gitattributes | 1 + .github/workflows/CD.yml | 8 +- .github/workflows/DOCS.yml | 27 ++- .gitignore | 1 + .luaurc | 2 + Doxyfile | 23 +- docs/index.txt | 7 + docs/install.txt | 60 +++++ docs/why.txt | 45 ++++ examples/asteroids/game.lua | 63 +++-- examples/dvdplayer/game.lua | 46 ++++ examples/launcher/game.lua | 7 +- npm/core-native-html5/README.md | 108 +++++++++ npm/core-native-html5/package.json | 17 ++ npm-gly-cli.json => npm/gly-cli/package.json | 2 +- npm/gly-engine/README.md | 3 + npm/gly-engine/package.json | 17 ++ src/cli/commands/build.lua | 22 +- src/cli/commands/info.lua | 3 +- src/cli/commands/tools.lua | 43 +++- src/cli/main.lua | 8 +- src/engine/core/ginga/draw.lua | 12 +- src/engine/core/ginga/main.lua | 14 +- src/engine/core/html5/core-native-html5.js | 230 +++++++++++++++++++ src/engine/core/html5/driver-wasmoon.js | 82 +++++++ src/engine/core/html5/engine.js | 159 ------------- src/engine/core/html5/index.html | 6 +- src/engine/core/love/draw.lua | 29 ++- src/engine/core/love/main.lua | 11 +- src/engine/core/native/main.lua | 25 +- src/engine/core/nintendo_wii/main.lua | 4 +- src/engine/core/repl/main.lua | 3 +- src/lib/cli/meta.lua | 15 +- src/lib/draw/fps.lua | 17 ++ src/lib/draw/poly.lua | 18 +- src/lib/engine/game.lua | 2 + src/lib/engine/hash.lua | 59 +++++ src/lib/engine/http.lua | 7 + src/lib/engine/i18n.lua | 183 +++++++++++++++ src/lib/engine/memory.lua | 107 +++++++++ src/lib/engine/module.lua | 55 ++++- src/lib/object/application.lua | 1 - src/lib/object/game.lua | 1 - src/lib/object/std.lua | 2 +- src/lib/protocol/http_ginga.lua | 1 - src/lib/util/lua.lua | 23 +- tests/test_lib_engine_hash.lua | 25 ++ tools/build_npm_cli.lua | 11 - tools/cd_npm_core-native-html5.lua | 8 + tools/cd_npm_gly-cli.lua | 13 ++ tools/cd_npm_gly-engine.lua | 9 + tools/doxygen_filter.lua | 179 +++++++++++++++ tools/doxygen_plugins.lua | 23 ++ tools/versionchecker.lua | 6 - 54 files changed, 1555 insertions(+), 298 deletions(-) create mode 100644 docs/index.txt create mode 100644 docs/install.txt create mode 100644 docs/why.txt create mode 100644 examples/dvdplayer/game.lua create mode 100644 npm/core-native-html5/README.md create mode 100644 npm/core-native-html5/package.json rename npm-gly-cli.json => npm/gly-cli/package.json (95%) create mode 100644 npm/gly-engine/README.md create mode 100644 npm/gly-engine/package.json create mode 100644 src/engine/core/html5/core-native-html5.js create mode 100644 src/engine/core/html5/driver-wasmoon.js delete mode 100644 src/engine/core/html5/engine.js create mode 100644 src/lib/engine/hash.lua create mode 100644 src/lib/engine/i18n.lua create mode 100644 src/lib/engine/memory.lua create mode 100644 tests/test_lib_engine_hash.lua delete mode 100644 tools/build_npm_cli.lua create mode 100644 tools/cd_npm_core-native-html5.lua create mode 100644 tools/cd_npm_gly-cli.lua create mode 100644 tools/cd_npm_gly-engine.lua create mode 100644 tools/doxygen_filter.lua create mode 100644 tools/doxygen_plugins.lua diff --git a/.gitattributes b/.gitattributes index f2cb800..89d2165 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,5 @@ *.css linguist-generated *.sh linguist-generated *.bat linguist-generated +*.txt linguist-language=Dox .luaurc linguist-language=Json diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 349a2ef..0deec41 100644 --- a/.github/workflows/CD.yml +++ b/.github/workflows/CD.yml @@ -10,8 +10,12 @@ permissions: id-token: write jobs: - npm-gly-cli: + npm: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: ['gly-cli', 'gly-engine', 'core-native-html5'] steps: - uses: actions/checkout@master @@ -26,7 +30,7 @@ jobs: registry-url: 'https://registry.npmjs.org' - run: | - lua tools/build_npm_cli.lua + lua tools/cd_npm_${{ matrix.package }}.lua - working-directory: ./dist run: | diff --git a/.github/workflows/DOCS.yml b/.github/workflows/DOCS.yml index 807b77a..3bcb033 100644 --- a/.github/workflows/DOCS.yml +++ b/.github/workflows/DOCS.yml @@ -1,8 +1,8 @@ name: docs env: - CLOUDFLARE_PROJECT_DOXYGEN: doxygen-engine - CLOUDFLARE_PROJECT_PONG: pong-engine + CLOUDFLARE_PROJECT_DOXYGEN: doxygen-gly + CLOUDFLARE_PROJECT_EXAMPLE: example-gly on: workflow_dispatch: @@ -21,11 +21,14 @@ jobs: steps: - uses: actions/checkout@master + - + run: | + docker run --rm -v $(pwd):/app -w /app rodrigodornelles/doxygen:lua lua tools/doxygen_plugins.lua - run: | docker run --rm -v $(pwd):/app -w /app rodrigodornelles/doxygen:lua doxygen - - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'workflow_dispatch' uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -33,20 +36,28 @@ jobs: projectName: ${{ env.CLOUDFLARE_PROJECT_DOXYGEN }} directory: html - pong: - runs-on: ubuntu-latest + example: if: github.event_name == 'push' || github.event.pull_request.draft == false + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + game: ['asteroids', 'dvdplayer', 'helloworld', 'launcher', 'pong'] steps: - uses: actions/checkout@master + - + uses: leafo/gh-actions-lua@v10 + with: + luaVersion: '5.4' - run: | - docker run --rm -v $(pwd):/app -w /app rodrigodornelles/doxygen:lua ./cli.sh build --core html5 ./examples/pong/game.lua --dist ./html/ + ./cli.sh build @${{ matrix.game }} --core html5 --dist ./html/ - - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'workflow_dispatch' uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - projectName: ${{ env.CLOUDFLARE_PROJECT_PONG }} + projectName: ${{ matrix.game }}-${{ env.CLOUDFLARE_PROJECT_EXAMPLE }} directory: html diff --git a/.gitignore b/.gitignore index 8831805..d24ea0a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ vendor/ dist/ html/ latex/ +doxygen/ .out \ No newline at end of file diff --git a/.luaurc b/.luaurc index 192531f..1cfeb33 100644 --- a/.luaurc +++ b/.luaurc @@ -16,10 +16,12 @@ "native_draw_color", "native_draw_flush", "native_draw_font", + "native_draw_image", "native_draw_line", "native_draw_rect", "native_draw_start", "native_draw_text", + "native_get_system_lang", "io" ] } diff --git a/Doxyfile b/Doxyfile index 6a7d43c..88efec5 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,9 +1,9 @@ # Custom Configs DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "Engine" -PROJECT_NUMBER = 0.0.6 +PROJECT_NAME = "Gly Game Engine" +PROJECT_NUMBER = 0.0.7 PROJECT_BRIEF = "Game engine in lua" -PROJECT_LOGO = +PROJECT_LOGO = assets/icon80x80.png OUTPUT_DIRECTORY = . RECURSIVE = YES CREATE_SUBDIRS = NO @@ -17,18 +17,19 @@ SHORT_NAMES = YES INLINE_GROUPED_CLASSES = YES INLINE_SIMPLE_STRUCTS = YES TYPEDEF_HIDES_STRUCT = YES -SOURCE_BROWSER = YES +SHOW_FILES = NO +SOURCE_BROWSER = NO VERBATIM_HEADERS = NO +REPEAT_BRIEF = NO PREDEFINED = DOXYGEN= PLANTUML_JAR_PATH = $(PLANTUML_JAR_PATH) PLANTUML_CFG_FILE = PLANTUML_INCLUDE_PATH = OUTPUT_LANGUAGE = English -FILE_PATTERNS = *.lua -FILTER_PATTERNS = *.lua=lua2dox +FILE_PATTERNS = *.lua *.txt +FILTER_PATTERNS = *="lua tools/doxygen_filter.lua" EXTENSION_MAPPING = .lua=C++ -INPUT = README.md SUPPORT.md src examples -USE_MDFILE_AS_MAINPAGE = README.md +INPUT = docs src examples MARKDOWN_ID_STYLE = GITHUB ALIASES += startjson=@startuml{json} ALIASES += endjson=@enduml @@ -40,6 +41,10 @@ ALIASES += endsalt=@enduml GENERATE_TREEVIEW = YES DISABLE_INDEX = NO FULL_SIDEBAR = NO +HTML_HEADER = doxygen/header.html HTML_EXTRA_STYLESHEET = $(DOXYGEN_AWESOME_PATH)/doxygen-awesome.css \ - $(DOXYGEN_AWESOME_PATH)/doxygen-awesome-sidebar-only.css + $(DOXYGEN_AWESOME_PATH)/doxygen-awesome-sidebar-only.css \ + $(DOXYGEN_AWESOME_PATH)/doxygen-awesome-sidebar-only-darkmode-toggle.css +HTML_EXTRA_FILES = $(DOXYGEN_AWESOME_PATH)/doxygen-awesome-darkmode-toggle.js \ + $(DOXYGEN_AWESOME_PATH)/doxygen-awesome-paragraph-link.js HTML_COLORSTYLE = LIGHT diff --git a/docs/index.txt b/docs/index.txt new file mode 100644 index 0000000..71455dd --- /dev/null +++ b/docs/index.txt @@ -0,0 +1,7 @@ +@mainpage + +@li @ref helloworld +@li @ref install +@li @ref why "Plataform Support" +@li [View the CLI/Engine source code](https://github.com/gamelly/gly-engine) +@li @ref why "Comparison between Game Engines" diff --git a/docs/install.txt b/docs/install.txt new file mode 100644 index 0000000..f856cd0 --- /dev/null +++ b/docs/install.txt @@ -0,0 +1,60 @@ +@page install How to Install + +@li @ref cli_download "download a unique lua file using wget" +@li @ref cli_download "download a unique lua file using curl" +@li @ref npm_install "install as system command using npm" +@li @ref npm_install "install as project dependency using npm" + +@anchor npm_install + +## using npm + +### global + +``` +npm install -g demoon @gamely/gly-cli +``` + +now you can execute as a global command! + +``` +gly-cli version +``` + +### local + +``` +npm install --dev @gamely/gly-cli +``` + +now you need use npx to run inside your project! + +``` +npx gly-cli version +``` + +--- + +@anchor cli_download + +## unique lua file + +@note @b Attention! that although it is a `.lua` file, @n it is obfuscating in a way, to browse the source code use `lua cli.lua cli-dump` + +### curl + +``` +wget get.gamely.com.br/cli.lua -o cli.lua +``` + +### wget + +``` +wget get.gamely.com.br/cli.lua +``` + +now you need a lua to execute a cli! + +``` +lua cli.lua version +``` diff --git a/docs/why.txt b/docs/why.txt new file mode 100644 index 0000000..958b328 --- /dev/null +++ b/docs/why.txt @@ -0,0 +1,45 @@ +@page why Features and Support + +## Comparison between Game Engines + +### In-game Resources + +| Native Resource | Gly Engine | Love2D | GameMaker | Unity | Godot | +| :--------------- | :--------- | :--------- | :--------- | :--------- | :--------- | +| Http Requests | Yes | No | No | Yes | Yes | +| Json Parse | Yes | No | No | No | Yes | +| I18N API | Yes | No | No | No | No | +| Cache API | Yes | No | No | Yes | No | +| QRCode API | Yes | No | No | No | No | +| Blend modes | No | No | Yes | Yes | Yes | +| Shadders | No | Yes | Yes | Yes | Yes | +| Image Tint | No | Yes | Yes | Yes | Yes | +| Image Rotate | No | Yes | Yes | Yes | Yes | +| Polygon Rotate | Yes | No | No | No | No | +| WASM HTML5 | Yes | No | Yes | Yes | Yes | +| Non-WASM HTML5 | Yes | No | Yes | No | No | + +### Development Resources + +| Native Resource | Gly Engine | Love2D | GameMaker | Unity | Godot | +| :--------------- | :--------- | :--------- | :--------- | :--------- | :----------- | +| CLI toolchain | Yes | No | No | No | Yes | +| REPL script | Lua | No | No | No | No | +| Code script | Lua, Haxe or JS | Lua | GMS | C# | C#, GDScript | +| Visual script | No | No | Yes | Yes | No | +| Package manager | Yes | No | Yes | Yes | No | +| IDE Programming | Yes | No | Yes | Yes | Yes | +| Unit Test tools | Yes | No | No | Yes | Yes | + + +## Plataform Support + +@call support + +### Development environment + +| Tier 1 | Linux, MacOS or BSD | main platform | +| :----- | :------------------ | :------------ | +| Tier 2 | NodeJS CLI | full support WIP +| Tier 3 | HTML5 IDE | support +| Tier 4 | Windows | support diff --git a/examples/asteroids/game.lua b/examples/asteroids/game.lua index 92399f9..cb16ed8 100644 --- a/examples/asteroids/game.lua +++ b/examples/asteroids/game.lua @@ -26,7 +26,25 @@ --! 8 -> 4: resume --! @enduml ---! @cond +local function i18n(std, game) + return { + ['pt-BR'] = { + ['lifes:'] = 'vidas:', + ['Continue'] = 'Continuar', + ['New Game'] = 'Novo Jogo', + ['Dificulty'] = 'Dificuldade', + ['Invincibility'] = 'Imortabilidade', + ['Object Limit'] = 'Limitador', + ['Graphics'] = 'Graficos', + ['fast'] = 'rapido', + ['pretty'] = 'bonito', + ['Language'] = 'Idioma', + ['Credits'] = 'Creditos', + ['Exit'] = 'Sair' + } + } +end + local function draw_logo(std, game, height, anim) anim = anim or 0 std.draw.font('sans', 32) @@ -143,7 +161,6 @@ local function asteroid_destroy(std, game, id) return score end ---! @endcond local function init(std, game) -- game @@ -205,7 +222,7 @@ local function loop(std, game) local keyv = std.key.press.down - std.key.press.up local keyh = std.key.press.right - std.key.press.left + std.key.press.enter + std.key.press.red if keyv ~= 0 and game.milis > game.menu_time + 250 then - game.menu = std.math.clamp(game.menu + keyv, game.player_pos_x == (game.width/2) and 2 or 1, 8) + game.menu = std.math.clamp(game.menu + keyv, game.player_pos_x == (game.width/2) and 2 or 1, 9) game.menu_time = game.milis end if keyh ~= 0 and game.milis > game.menu_time + 100 then @@ -226,8 +243,10 @@ local function loop(std, game) game.graphics_fastest = std.math.clamp(game.graphics_fastest + keyh, 0, 1) game.fps_max = 100 elseif game.menu == 7 then - game.state = 2 + std.i18n.next_language() elseif game.menu == 8 then + game.state = 2 + elseif game.menu == 9 then std.game.exit() end end @@ -372,30 +391,35 @@ local function draw(std, game) if game.state == 1 then local s2 = 0 local h = game.height/16 - local graphics = game.graphics_fastest == 1 and 'rapido' or 'bonito' + local hmenu = (h*(4+game.menu)) + 24 + local language = std.i18n.get_language() + local graphics = game.graphics_fastest == 1 and 'fast' or 'pretty' local s = draw_logo(std, game, h*2) std.draw.font('sans', 16) std.draw.color(std.color.white) if game.player_pos_x ~= (game.width/2) then - std.draw.text(game.width/2 - s, h*6, 'Continuar') + std.draw.text(game.width/2 - s, h*5, 'Continue') end - std.draw.text(game.width/2 - s, h*7, 'Novo Jogo') - std.draw.text(game.width/2 - s, h*8, 'Dificuldade') - std.draw.text(game.width/2 - s, h*9, 'Imortalidade') - std.draw.text(game.width/2 - s, h*10, 'Limitador') - std.draw.text(game.width/2 - s, h*11, 'Graficos') - std.draw.text(game.width/2 - s, h*12, 'Creditos') - std.draw.text(game.width/2 - s, h*13, 'Sair') - std.draw.line(game.width/2 - s, (h*(5+game.menu)) + 24, game.width/2 + s, (h*(5+game.menu)) + 24) + std.draw.text(game.width/2 - s, h*6, 'New Game') + std.draw.text(game.width/2 - s, h*7, 'Dificulty') + std.draw.text(game.width/2 - s, h*8, 'Invincibility') + std.draw.text(game.width/2 - s, h*9, 'Object Limit') + std.draw.text(game.width/2 - s, h*10, 'Graphics') + std.draw.text(game.width/2 - s, h*11, 'Language') + std.draw.text(game.width/2 - s, h*12, 'Credits') + std.draw.text(game.width/2 - s, h*13, 'Exit') + std.draw.line(game.width/2 - s, hmenu, game.width/2 + s, hmenu) std.draw.color(std.color.red) s2=std.draw.text(game.level) - std.draw.text(game.width/2 + s - s2, h*8, game.level) + std.draw.text(game.width/2 + s - s2, h*7, game.level) s2=std.draw.text(game.imortal) - std.draw.text(game.width/2 + s - s2, h*9, game.imortal) + std.draw.text(game.width/2 + s - s2, h*8, game.imortal) s2=std.draw.text(game.asteroids_max) - std.draw.text(game.width/2 + s - s2, h*10, game.asteroids_max) + std.draw.text(game.width/2 + s - s2, h*9, game.asteroids_max) s2=std.draw.text(graphics) - std.draw.text(game.width/2 + s - s2, h*11, graphics) + std.draw.text(game.width/2 + s - s2, h*10, graphics) + s3=std.draw.text(language) + std.draw.text(game.width/2 + s - s3, h*11, language) return elseif game.state == 2 then local height = game.height/4 @@ -475,11 +499,12 @@ local P = { version='1.0.0' }, config = { - require = 'math random', + require = 'math math.random i18n', fps_drop = 5, fps_time = 5 }, callbacks={ + i18n=i18n, init=init, loop=loop, draw=draw, diff --git a/examples/dvdplayer/game.lua b/examples/dvdplayer/game.lua new file mode 100644 index 0000000..02790cd --- /dev/null +++ b/examples/dvdplayer/game.lua @@ -0,0 +1,46 @@ +local function init(std, game) + game.x = game.width/2 + game.y = game.height/2 + game.size = 80 + game.hspeed = game.width/5000 + game.vspeed = game.height/4000 +end + +local function loop(std, game) + game.x = std.math.clamp(game.x + (game.hspeed * game.dt), 0, game.width - game.size) + game.y = std.math.clamp(game.y + (game.vspeed * game.dt), 0, game.height - game.size) + if game.x == 0 or game.x == game.width - game.size then + game.hspeed = -1 * game.hspeed + end + if game.y == 0 or game.y == game.height - game.size then + game.vspeed = -1 * game.vspeed + end +end + +local function draw(std, game) + std.draw.clear(std.color.black) + std.draw.image('icon80x80.png', game.x, game.y) +end + +local function exit(std, game) +end + +local P = { + meta={ + title='DVD Player', + author='RodrigoDornelles', + description='a logo bouncing between the corners', + version='1.0.0' + }, + assets={ + 'assets/icon80x80.png:icon80x80.png' + }, + callbacks={ + init=init, + loop=loop, + draw=draw, + exit=exit + } +} + +return P; diff --git a/examples/launcher/game.lua b/examples/launcher/game.lua index 35dfedb..2f9f360 100644 --- a/examples/launcher/game.lua +++ b/examples/launcher/game.lua @@ -1,5 +1,4 @@ ---! @file examples/launcher/game.lua ---! +--! @par Game FSM --! @startuml --! hide empty description --! skinparam State { @@ -39,7 +38,6 @@ --! 9 --> [*] --! @enduml ---! @cond local function next_state(game, new_state) if game._state + 1 == new_state then game._state = new_state @@ -64,7 +62,6 @@ local function halt_state(game) end end end ---! @endcond local function init(std, game) if not game._state then @@ -239,7 +236,7 @@ local P = { version='1.0.0' }, config={ - require='http random math csv load' + require='http math.random math csv load' }, callbacks={ init=init, diff --git a/npm/core-native-html5/README.md b/npm/core-native-html5/README.md new file mode 100644 index 0000000..61513eb --- /dev/null +++ b/npm/core-native-html5/README.md @@ -0,0 +1,108 @@ +
+

:mechanical_arm:
GLY Engine
powered by gamely.com.br

+
+ +> create your own game-engine with just javascript. + +## Example + +### The Game +```javascript +function awesome_game(loop, draw, keys) { + let color = 0x00FFFFFF + + loop.callback(() => { + + }) + draw.callback(() => { + draw.color(0x000000FF) + draw.rect(0, 0, 0, draw.width, draw.heigth) + draw.color(color) + draw.rect(0, 10, 20, 30, 40) + }) + keys.callback((key, press) => { + if (key == 'KeyZ' && press) { + color = 0xFF0000FF + } + }) +} +``` + +### The Engine + +```javascript + +const engine = { + loop: { + callback: (f) => {engine.callback_loop = f} + }, + draw: { + color: gly.global.get('native_draw_color'), + rect: gly.global.get('native_draw_rect'), + callback: (f) => {engine.callback_draw = f} + }, + keys: { + callback: (f) => {engine.callback_keys = f} + }, +} + +gly.global.set('native_callback_init', (width, heigth, game) => { + game(engine.loop, engine.draw, engine.keys) + engine.draw.width = width + engine.draw.heigth = heigth +}) + +gly.init('#gameCanvas') +gly.load(awesome_game) + +gly.global.set('native_callback_loop', engine.callback_loop) +gly.global.set('native_callback_draw', engine.callback_draw) +gly.global.set('native_callback_keyboard', engine.callback_keys) + +function updateKey(ev) { + gly.input(ev.code, ev.type === 'keydown') +} +window.addEventListener('keydown', updateKey) +window.addEventListener('keyup', updateKey) + +function tick() { + gly.update() + window.requestAnimationFrame(tick) +} +tick() +``` + +## Cheatcheet + +### Direct API + + * **gly.init(canvas_selector)** + * **gly.load(game_file_text)** + * **gly.input(key, value)** + * **gly.error(behavior)** + * **gly.resize()** + * **gly.update(milis)** + * **gly.global.set(name, value)** + * **gly.global.get(name)** + +### Functions + + * **native_draw_start()** + * **native_draw_flush()** + * **native_draw_clear(color)** + * **native_draw_color(color)** + * **native_draw_font(name, size)** + * **native_draw_rect(mode, x, y, width, heigth)** + * **native_draw_line(x1, y1, x2, y2)** + * **native_draw_text(x, y, text)** + * **native_draw_poly(mode, verts, x, y, scale, angle, ox, oy)** + * **native_dict_http** + * **native_dict_json** + +### Callbacks + + * **native_callback_loop(milis)** + * **native_callback_draw()** + * **native_callback_resize(width, height)** + * **native_callback_keyboard(key, value)** + * **native_callback_init(width, height, game)** diff --git a/npm/core-native-html5/package.json b/npm/core-native-html5/package.json new file mode 100644 index 0000000..4e121cf --- /dev/null +++ b/npm/core-native-html5/package.json @@ -0,0 +1,17 @@ +{ + "name": "@gamely/core-native-html5", + "version": "{{version}}", + "author": "RodrigoDornelles", + "license": "Apache-2.0", + "homepage": "https://gamely.com.br", + "repository": "https://github.com/gamelly/gly-engine", + "funding": "https://github.com/sponsors/RodrigoDornelles", + "bugs": "https://github.com/gamelly/gly-engine/issues", + "description": "create your own game-engine with just javascript.", + "main": "dist/index.js", + "keywords": [ + "game engine", + "game", + "engine" + ] +} diff --git a/npm-gly-cli.json b/npm/gly-cli/package.json similarity index 95% rename from npm-gly-cli.json rename to npm/gly-cli/package.json index 35af420..5366ee8 100644 --- a/npm-gly-cli.json +++ b/npm/gly-cli/package.json @@ -1,6 +1,6 @@ { "name": "@gamely/gly-cli", - "version": "0.0.6", + "version": "{{version}}", "author": "RodrigoDornelles", "license": "Apache-2.0", "homepage": "https://gamely.com.br", diff --git a/npm/gly-engine/README.md b/npm/gly-engine/README.md new file mode 100644 index 0000000..c7a3718 --- /dev/null +++ b/npm/gly-engine/README.md @@ -0,0 +1,3 @@ +
+

:mechanical_arm:
GLY Engine
powered by gamely.com.br

+
diff --git a/npm/gly-engine/package.json b/npm/gly-engine/package.json new file mode 100644 index 0000000..db64311 --- /dev/null +++ b/npm/gly-engine/package.json @@ -0,0 +1,17 @@ +{ + "name": "@gamely/gly-engine", + "version": "{{version}}", + "author": "RodrigoDornelles", + "license": "Apache-2.0", + "homepage": "https://gamely.com.br", + "repository": "https://github.com/gamelly/gly-engine", + "funding": "https://github.com/sponsors/RodrigoDornelles", + "bugs": "https://github.com/gamelly/gly-engine/issues", + "description": "Game Engine written in 100% lua that runs in a vacuum.", + "main": "dist/index.lua", + "keywords": [ + "game engine", + "game", + "engine" + ] +} diff --git a/src/cli/commands/build.lua b/src/cli/commands/build.lua index 37ad9c4..b8db00f 100644 --- a/src/cli/commands/build.lua +++ b/src/cli/commands/build.lua @@ -38,7 +38,8 @@ local function build(args) 'src/engine/meta/html5_webos/appinfo.json', 'src/engine/core/html5/index.html', 'src/engine/core/html5/index.html', - 'src/engine/core/html5/engine.js', + 'src/engine/core/html5/driver-wasmoon.js', + 'src/engine/core/html5/core-native-html5.js', 'assets/icon80x80.png' } }, @@ -53,23 +54,11 @@ local function build(args) 'src/engine/meta/html5_tizen/.tproject', 'src/engine/core/html5/index.html', 'src/engine/core/html5/index.html', - 'src/engine/core/html5/engine.js', + 'src/engine/core/html5/driver-wasmoon.js', + 'src/engine/core/html5/core-native-html5.js', 'assets/icon80x80.png' } }, - html5_ginga={ - src='src/engine/core/native/main.lua', - post_exe='ginga dist/main.ncl -s '..screen, - pipeline={ - zeebo_meta.late(dist..'game.lua'):file(dist..'index.html'):pipe() - }, - extras={ - 'src/engine/meta/html5_ginga/main.ncl', - 'src/engine/core/html5/index.html', - 'src/engine/core/html5/index.html', - 'src/engine/core/html5/engine.js', - } - }, html5={ src='src/engine/core/native/main.lua', pipeline={ @@ -77,7 +66,8 @@ local function build(args) }, extras={ 'src/engine/core/html5/index.html', - 'src/engine/core/html5/engine.js' + 'src/engine/core/html5/driver-wasmoon.js', + 'src/engine/core/html5/core-native-html5.js' } }, nintendo_wii={ diff --git a/src/cli/commands/info.lua b/src/cli/commands/info.lua index f493228..e297b8a 100644 --- a/src/cli/commands/info.lua +++ b/src/cli/commands/info.lua @@ -21,7 +21,6 @@ local help_message = "Available commands:\n" .."- ginga: Runs the Ginga core.\n" .."- html5_webos: Builds for HTML5 on WebOS.\n" .."- html5_tizen: Builds for HTML5 on Tizen.\n" - .."- html5_ginga: Runs the Ginga core for HTML5.\n" .."- html5: Runs the standard HTML5 core.\n" .."- nintendo_wii: Builds for the Nintendo Wii.\n" .."\n" @@ -44,7 +43,7 @@ local help_message = "Available commands:\n" .."- To run a game: ./cli.sh run ./examples/asteroids/game.lua " .. "-" .. "-core repl\n" .."- To display metadata: ./cli.sh meta ./examples/asteroids/game.lua\n" -local version_message = '0.0.6' +local version_message = '0.0.7' local function help() return true, help_message diff --git a/src/cli/commands/tools.lua b/src/cli/commands/tools.lua index 4909bd2..0c13119 100644 --- a/src/cli/commands/tools.lua +++ b/src/cli/commands/tools.lua @@ -1,5 +1,6 @@ local zeebo_compiler = require('src/lib/cli/compiler') local zeebo_bundler = require('src/lib/cli/bundler') +local zeebo_fs = require('src/lib/cli/fs') local function bundler(args) local path, file = args.file:match("(.-)([^/\\]+)$") @@ -11,16 +12,56 @@ local function compiler(args) end local function love_zip(args) - return false, 'not implemented!' + os.execute('mkdir -p '..args.dist..'_love') + os.execute('mv '..args.path..'/* '..args.dist..'_love 2> /dev/null') + local zip_pid = io.popen('cd '..args.dist..'_love && zip -9 -r Game.love .') + local stdout = zip_pid:read('*a') + local ok = zip_pid:close() + zeebo_fs.move(args.dist..'_love/Game.love', args.dist..'Game.love') + zeebo_fs.clear(args.dist..'_love') + os.remove(args.dist..'_love') + return ok, stdout end local function love_exe(args) return false, 'not implemented!' end +local function haxe_build(args) + local game_name = args.game + local game_file, file_error = io.open(game_name, 'r') + local game_content = game_file and game_file:read('*a') + + if file_error then + return false, file_error + end + + local pattern_utf8 = '_G%.require%("lua%-utf8"%)' + local replace_utf8 = 'select(2, pcall(require, "lua-utf8")) or select(2, pcall(require, "utf8")) or string' + local pattern_object = 'std%.(%w+):(%w+)' + local replace_object = 'std.%1.%2' + + game_content = game_content:gsub(pattern_utf8, replace_utf8) + game_content = game_content:gsub(pattern_object, replace_object) + + game_file:close() + game_file, file_error = io.open(game_name, 'w') + + if file_error then + return false, file_error + end + + game_file:write(game_content) + game_file:close() + + return true +end + + local P = { bundler = bundler, compiler = compiler, + ['tool-haxe-build'] = haxe_build, ['tool-love-zip'] = love_zip, ['tool-love-exe'] = love_exe } diff --git a/src/cli/main.lua b/src/cli/main.lua index 003b195..94b3ee7 100644 --- a/src/cli/main.lua +++ b/src/cli/main.lua @@ -1,3 +1,6 @@ +--! @short gly-cli +--! @par Command List +--! @call commands local os = require('os') local zeebo_argparse = require('src/lib/cli/argparse') @@ -39,9 +42,12 @@ local command = zeebo_argparse.from(arg) .add_next_value('file', {required=true}) .add_option_get('dist', {default='a.out'}) -- + .add_subcommand('tool-haxe-build', commands_tools) + .add_next_value('game', {required=true}) + -- .add_subcommand('tool-love-zip', commands_tools) .add_next_value('path', {required=true}) - .add_option_get('dist', {required=true}) + .add_option_get('dist', {default='./dist/'}) -- .add_subcommand('tool-love-exe', commands_tools) .add_next_value('file', {required=true}) diff --git a/src/engine/core/ginga/draw.lua b/src/engine/core/ginga/draw.lua index 21a9974..55c6ccc 100644 --- a/src/engine/core/ginga/draw.lua +++ b/src/engine/core/ginga/draw.lua @@ -3,6 +3,7 @@ local math = require('math') --! @cond local canvas = nil local game = nil +local std = nil --! @endcond local function color(c) @@ -41,11 +42,20 @@ local function line(x1, y1, x2, y2) canvas:drawLine(x1, y1, x2, y2) end -local function install(std, lgame, application, ginga) +local function image(src, x, y) + local image = std.mem.cache('image'..src, function() + return canvas:new('../assets/'..src) + end) + canvas:compose(x, y, image) +end + +local function install(lstd, lgame, application, ginga) canvas = ginga.canvas game = lgame + std = lstd std = std or {} std.draw = std.draw or {} + std.draw.image=image std.draw.clear=clear std.draw.color=color std.draw.rect=rect diff --git a/src/engine/core/ginga/main.lua b/src/engine/core/ginga/main.lua index 3206080..a0b2265 100644 --- a/src/engine/core/ginga/main.lua +++ b/src/engine/core/ginga/main.lua @@ -4,6 +4,8 @@ local engine_fps = require('src/lib/engine/fps') local engine_math = require('src/lib/engine/math') local engine_game = require('src/lib/engine/game') local engine_http = require('src/lib/engine/http') +local engine_i18n = require('src/lib/engine/i18n') +local engine_memory = require('src/lib/engine/memory') local engine_color = require('src/lib/object/color') local engine_keys = require('src/engine/core/ginga/keys') local engine_loop = require('src/engine/core/ginga/loop') @@ -67,6 +69,10 @@ local function install(evt, gamefile) time={1, 10, 30, 40, 60, 90} } + local system_language = function() + return 'pt-BR' + end + application = zeebo_module.loadgame(gamefile) if not application then error('game not loaded!') @@ -84,14 +90,16 @@ local function install(evt, gamefile) :package('@keys', engine_keys) :package('@loop', engine_loop) :package('@draw', engine_draw, ginga) - :package('@draw_fps', engine_draw_fps) - :package('@draw_poly', engine_draw_poly, polygons) + :package('@draw.fps', engine_draw_fps) + :package('@draw.poly', engine_draw_poly, polygons) + :package('@memory', engine_memory) :package('load', zeebo_module.load) :package('csv', engine_encoder, library_csv) :package('json', engine_encoder, library_json) :package('math', engine_math.clib) - :package('random', engine_math.clib_random) + :package('math.random', engine_math.clib_random) :package('http', engine_http, protocol_http_ginga) + :package('i18n', engine_i18n, system_language) :register(register_event_loop) :register(register_fixed_loop) :run() diff --git a/src/engine/core/html5/core-native-html5.js b/src/engine/core/html5/core-native-html5.js new file mode 100644 index 0000000..327a799 --- /dev/null +++ b/src/engine/core/html5/core-native-html5.js @@ -0,0 +1,230 @@ +const engine = { + stop: false, + file: './main.lua', + error: { + callback: null, + capture: false, + console: false, + canvas: false, + stop: false + }, + images: {}, + listen: {}, + global: { + native_draw_start: () => { + engine.canvas_ctx.clearRect(0, 0, engine.canvas_element.width, engine.canvas_element.height) + }, + native_draw_flush: () => {}, + native_draw_clear: (color) => { + engine.canvas_ctx.fillStyle = '#' + color.toString(16).padStart(8, '0') + engine.canvas_ctx.fillRect(0, 0, engine.canvas_element.width, engine.canvas_element.height) + }, + native_draw_color: (color) => { + const hex = '#' + color.toString(16).padStart(8, '0') + engine.canvas_ctx.strokeStyle = hex + engine.canvas_ctx.fillStyle = hex + }, + native_draw_line: (x1, y1, x2, y2) => { + engine.canvas_ctx.beginPath() + engine.canvas_ctx.moveTo(x1, y1) + engine.canvas_ctx.lineTo(x2, y2) + engine.canvas_ctx.stroke() + }, + native_draw_rect: (mode, x, y, w, h) => { + mode === 1 ? engine.canvas_ctx.strokeRect(x, y, w, h) : engine.canvas_ctx.fillRect(x, y, w, h) + }, + native_draw_font: (name, size) => { + const font_size = size || name + const font_name = 'sans' + engine.canvas_ctx.font = `${font_size}px ${font_name}`; + engine.canvas_ctx.textBaseline = 'top' + engine.canvas_ctx.textAlign = 'left' + }, + native_draw_text: (x, y, text) => { + if (x && y) { + engine.canvas_ctx.fillText(text, x, y) + } + const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = engine.canvas_ctx.measureText(text || x) + return [width, actualBoundingBoxAscent + actualBoundingBoxDescent] + }, + native_draw_image: (src, x, y) => { + if (!(src in engine.images)) { + engine.images[src] = document.createElement('img') + engine.images[src].src = src + engine.images[src].onload = function() { + engine.images[src].attributes.done = 'true' + } + } + if (engine.images[src].attributes.done) { + engine.canvas_ctx.drawImage(engine.images[src], x, y) + } + }, + native_get_system_language: () => { + return navigator.language + }, + native_dict_poly: { + poly2: (mode, verts, x, y, scale = 1, angle = 0, ox = 0, oy = 0) => { + let index = 0 + engine.canvas_ctx.beginPath() + while (index < verts.length) { + const px = verts[index]; + const py = verts[index + 1]; + const xx = x + ((ox - px) * -scale * Math.cos(angle)) - ((oy - py) * -scale * Math.sin(angle)); + const yy = y + ((oy - px) * -scale * Math.sin(angle)) + ((ox - py) * -scale * Math.cos(angle)); + if (index < 2) { + engine.canvas_ctx.moveTo(xx, yy) + } else { + engine.canvas_ctx.lineTo(xx, yy) + } + index += 2; + } + engine.canvas_close[mode]() + } + }, + native_dict_http: { + handler: (self) => { + const method = self.method + const headers = new Headers(self.headers_dict) + const params = new URLSearchParams(self.params_dict) + const url = `${self.url}` + '?' + params.toString() + const body = ['HEAD', 'GET'].includes(method) ? null : self.body_content + self.promise() + fetch(url, { + body: body, + method: method, + headers: headers + }) + .then((response) => { + self.set('ok', response.ok) + self.set('status', response.status) + return response.text() + }) + .then((content) => { + self.set('body', content) + self.resolve() + }) + .catch((error) => { + self.set('ok', false) + self.set('error', `${error}`) + self.resolve() + }) + } + }, + native_dict_json: { + encode: JSON.stringify, + decode: JSON.parse + } + } +} + +function errorController(func) { + if (engine.stop) { + return + } + if (!engine.error.capture) { + return func() + } + try { + func() + } + catch(e) { + if (engine.error.console) { + console.log(e) + } + if (engine.error.stop) { + engine.stop = true + } + if (engine.error.canvas) { + engine.global.native_draw_start() + engine.global.native_draw_clear(0x0000FFFF) + engine.global.native_draw_color(0xFFFFFFFF) + engine.global.native_draw_text(8, 16, 'Fatal ERROR') + engine.global.native_draw_text(8, 32, e.toString()) + } + if (engine.error.callback) { + engine.error.callback(e) + } + } +} + +function resizeCanvas() { + if (engine.body_element.clientWidth > engine.body_element.clientHeight) { + engine.canvas_element.height = engine.body_element.clientHeight + engine.canvas_element.width = engine.body_element.clientWidth + } + else { + engine.canvas_element.height = Math.floor(engine.body_element.clientHeight / 2) + engine.canvas_element.width = engine.body_element.clientWidth + } +} + +const gly = { + init: (canvas_selector) => { + engine.body_element = document.querySelector('body') + engine.canvas_element = document.querySelector(canvas_selector) + engine.canvas_ctx = engine.canvas_element.getContext("2d") + engine.canvas_close = [ + () => engine.canvas_ctx.fill(), + () => { + engine.canvas_ctx.closePath() + engine.canvas_ctx.stroke() + }, + () => engine.canvas_ctx.stroke() + ] + resizeCanvas() + }, + load: (game_file) => { + const {width, height} = engine.canvas_element + errorController(() => { + engine.listen.native_callback_init(width, height, game_file) + }) + }, + input: (key, value) => { + errorController(() => { + engine.listen.native_callback_keyboard(key, value) + }) + }, + error: (behavior, error_callback) => { + const silent = behavior.includes('silent') + engine.error.capture = !behavior.includes('default') + engine.error.console = !silent && behavior.includes('console') + engine.error.canvas = !silent && behavior.includes('canvas') + engine.error.stop = !silent && behavior.includes('stop') + engine.error.callback = error_callback + }, + resize: () => { + resizeCanvas() + const {width, height} = engine.canvas_element + errorController(() => { + engine.listen.native_callback_resize(width, height) + }) + }, + update: (milis) => { + errorController(() => { + engine.listen.native_callback_loop(milis) + engine.listen.native_callback_draw() + }) + }, + engine: { + set: (file_name) => engine.file = file_name, + get: () => engine.file + }, + global: { + set: (var_name, value) => { + engine.listen[var_name] = value + }, + get: (var_name) => { + return engine.global[var_name] + } + } +} + +if (typeof exports === 'object' && typeof module !== 'undefined') { + module.exports = gly; +} else if (typeof define === 'function' && define.amd) { + define([], function() { + return gly; + }); +} else { + window.gly = gly; +} diff --git a/src/engine/core/html5/driver-wasmoon.js b/src/engine/core/html5/driver-wasmoon.js new file mode 100644 index 0000000..cd55350 --- /dev/null +++ b/src/engine/core/html5/driver-wasmoon.js @@ -0,0 +1,82 @@ +import { LuaFactory, LuaMultiReturn } from 'https://cdn.jsdelivr.net/npm/wasmoon@1.16.0/+esm' + +if (!gly) { + error('gly is not loaded!') +} + +gly.wasmoon = async (game_file) => { + const factory = new LuaFactory() + const engine_file = gly.engine.get() + const lua = await factory.createEngine() + const engine_response = await fetch(engine_file) + const engine_lua = await engine_response.text() + await lua.doString(engine_lua) + + gly.global.set('native_callback_init', lua.global.get('native_callback_init')) + gly.global.set('native_callback_loop', lua.global.get('native_callback_loop')) + gly.global.set('native_callback_draw', lua.global.get('native_callback_draw')) + gly.global.set('native_callback_resize', lua.global.get('native_callback_resize')) + gly.global.set('native_callback_keyboard', lua.global.get('native_callback_keyboard')) + lua.global.set('native_draw_start', gly.global.get('native_draw_start')) + lua.global.set('native_draw_flush', gly.global.get('native_draw_flush')) + lua.global.set('native_draw_clear', gly.global.get('native_draw_clear')) + lua.global.set('native_draw_color', gly.global.get('native_draw_color')) + lua.global.set('native_draw_font', gly.global.get('native_draw_font')) + lua.global.set('native_draw_rect', gly.global.get('native_draw_rect')) + lua.global.set('native_draw_line', gly.global.get('native_draw_line')) + lua.global.set('native_draw_image', gly.global.get('native_draw_image')) + lua.global.set('native_dict_http', gly.global.get('native_dict_http')) + lua.global.set('native_dict_json', gly.global.get('native_dict_json')) + lua.global.set('native_dict_poly', gly.global.get('native_dict_poly')) + lua.global.set('native_draw_text', (x, y, text) => { + const native_draw_text = gly.global.get('native_draw_text') + return LuaMultiReturn.from(native_draw_text(x, y, text)) + }) + + gly.error('stop, canvas, console') + gly.init('#gameCanvas') + + if (typeof game_file === 'string' && !game_file.includes('\n')) { + const game_response = await fetch(game_file) + gly.load(await game_response.text()) + } else { + gly.load(game_file) + } + + const keys = [ + [403, 'red'], + [404, 'green'], + [405, 'yellow'], + [406, 'blue'], + ['KeyZ', 'red'], + ['KeyX', 'green'], + ['KeyC', 'yellow'], + ['KeyV', 'blue'], + ['Enter', 'enter'], + ['ArrowUp', 'up'], + ['ArrowDown', 'down'], + ['ArrowLeft', 'left'], + ['ArrowRight', 'right'], + ]; + + function updateSize() { + gly.resize() + } + + function updateKey(ev) { + const key = keys.find(key => key[0] == ev.code) + if (key) { + gly.input(key[1], Number(ev.type === 'keydown')) + } + } + + function updateLoop() { + window.requestAnimationFrame(updateLoop); + gly.update((new Date()).getTime()) + } + + window.addEventListener("resize", updateSize); + window.addEventListener('keydown', updateKey) + window.addEventListener('keyup', updateKey) + window.requestAnimationFrame(updateLoop); +} diff --git a/src/engine/core/html5/engine.js b/src/engine/core/html5/engine.js deleted file mode 100644 index c6c7e09..0000000 --- a/src/engine/core/html5/engine.js +++ /dev/null @@ -1,159 +0,0 @@ -import { LuaFactory, LuaMultiReturn } from 'https://cdn.jsdelivr.net/npm/wasmoon@1.16.0/+esm' - -document.addEventListener('DOMContentLoaded', async () => { - const factory = new LuaFactory() - const lua = await factory.createEngine() - - const engine_response = await fetch('./main.lua') - const game_response = await fetch('./game.lua') - - const engine_lua = await engine_response.text() - const game_lua = await game_response.text() - - const body_element = document.querySelector('body') - const canvas_element = document.querySelector('#gameCanvas') - const canvas_ctx = canvas_element.getContext("2d") - const canvas_close = [ - () => canvas_ctx.fill(), - () => { - canvas_ctx.closePath() - canvas_ctx.stroke() - }, - () => canvas_ctx.stroke() - ] - - lua.global.set('native_draw_start', () => { - canvas_ctx.clearRect(0, 0, canvas_element.width, canvas_element.height) - }) - lua.global.set('native_draw_flush', () => {}) - lua.global.set('native_draw_clear', (color) => { - canvas_ctx.fillStyle = '#' + color.toString(16).padStart(8, '0') - canvas_ctx.fillRect(0, 0, canvas_element.width, canvas_element.height) - }) - lua.global.set('native_draw_color', (color) => { - const hex = '#' + color.toString(16).padStart(8, '0') - canvas_ctx.strokeStyle = hex - canvas_ctx.fillStyle = hex - }) - lua.global.set('native_draw_line', (x1, y1, x2, y2) => { - canvas_ctx.beginPath() - canvas_ctx.moveTo(x1, y1) - canvas_ctx.lineTo(x2, y2) - canvas_ctx.stroke() - }) - lua.global.set('native_draw_rect', (mode, x, y, w, h) => mode === 1? - canvas_ctx.strokeRect(x, y, w, h): - canvas_ctx.fillRect(x, y, w, h) - ) - lua.global.set('native_draw_font', (name, size) => {}) - lua.global.set('native_draw_text', (x, y, text) => { - x && y && canvas_ctx.fillText(text, x, y) - const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = canvas_ctx.measureText(text || x) - return LuaMultiReturn.from([width, actualBoundingBoxAscent + actualBoundingBoxDescent]) - }) - lua.global.set('native_dict_poly', { - poly2: (mode, verts, x, y, scale = 1, angle = 0, ox = 0, oy = 0) => { - let index = 0 - canvas_ctx.beginPath() - while (index < verts.length) { - const px = verts[index]; - const py = verts[index + 1]; - const xx = x + ((ox - px) * -scale * Math.cos(angle)) - ((ox - py) * -scale * Math.sin(angle)); - const yy = y + ((oy - px) * -scale * Math.sin(angle)) + ((oy - py) * -scale * Math.cos(angle)); - if (index < 2) { - canvas_ctx.moveTo(xx, yy) - } else { - canvas_ctx.lineTo(xx, yy) - } - index = index + 2; - } - canvas_close[mode]() - } - }) - lua.global.set('native_dict_http', { - handler: (self) => { - const method = self.method - const headers = new Headers(self.headers_dict) - const params = new URLSearchParams(self.params_dict) - const url = `${self.url}` + '?' + params.toString() - const body = ['HEAD', 'GET'].includes(method)? null: self.body_content - self.promise() - fetch(url, { - body: body, - method: method, - headers: headers - }) - .then((response) => { - self.set('ok', response.ok) - self.set('status', response.status) - return response.text() - }) - .then((content) => { - self.set('body', content) - self.resolve() - }) - .catch((error) => { - self.set('ok', false) - self.set('error', `${error}`) - self.resolve() - }) - } - }) - lua.global.set('native_dict_json', { - encode: JSON.stringify, - decode: JSON.parse - }) - - if (body_element.clientWidth > body_element.clientHeight) { - canvas_element.height = body_element.clientHeight - canvas_element.width = body_element.clientWidth - } - else { - canvas_element.height = Math.floor(body_element.clientHeight / 2) - canvas_element.width = body_element.clientWidth - } - - await lua.doString(engine_lua) - const engine_callbacks = { - init: lua.global.get('native_callback_init'), - update: lua.global.get('native_callback_loop'), - draw: lua.global.get('native_callback_draw'), - keyboard: lua.global.get('native_callback_keyboard'), - } - engine_callbacks.init(canvas_element.width, canvas_element.height, game_lua) - - setTimeout(() => { - const keys = [ - [403, 'red'], - [404, 'green'], - [405, 'yellow'], - [406, 'blue'], - ['KeyZ', 'red'], - ['KeyX', 'green'], - ['KeyC', 'yellow'], - ['KeyV', 'blue'], - ['Enter', 'enter'], - ['ArrowUp', 'up'], - ['ArrowDown', 'down'], - ['ArrowLeft', 'left'], - ['ArrowRight', 'right'], - ]; - - const keyHandler = (ev) => keys - .filter(key => [ev.code, ev.keyCode].includes(key[0])) - .map(key => engine_callbacks.keyboard(key[1], Number(ev.type === 'keydown'))) - - window.addEventListener('keydown', keyHandler) - window.addEventListener('keyup', keyHandler) - }, 100) - - const tick = () => { - const now = new Date() - const milis = now.getTime() - engine_callbacks.update(milis) - engine_callbacks.draw() - window.requestAnimationFrame(tick) - } - - tick() -}) diff --git a/src/engine/core/html5/index.html b/src/engine/core/html5/index.html index 1017fa4..4620874 100644 --- a/src/engine/core/html5/index.html +++ b/src/engine/core/html5/index.html @@ -36,6 +36,10 @@
- + + + diff --git a/src/engine/core/love/draw.lua b/src/engine/core/love/draw.lua index 9fc7f4c..4870040 100644 --- a/src/engine/core/love/draw.lua +++ b/src/engine/core/love/draw.lua @@ -48,26 +48,39 @@ local function triangle(mode, x1, y1, x2, y2, x3, y3) end end -local function font(name, size) - if type(name) == 'number' and not size then +local function font(std, name, size) + if not size and type(name) == 'number' then size = name name = 'Tiresias' end - local index = 'font_'..tostring(name)..tostring(size) - if not _G[index] then - _G[index] = love.graphics.newFont(size) - end - love.graphics.setFont(_G[index]) + local f = std.mem.cache('font_'..name..tostring(size), function() + return love.graphics.newFont(size) + end) + love.graphics.setFont(f) +end + +local function image(std, src, x, y) + local r, g, b, a = love.graphics.getColor() + local image = std.mem.cache('image'..src, function() + return love.graphics.newImage(src) + end) + love.graphics.setColor(0xFF, 0xFF, 0xFF, 0xFF) + love.graphics.draw(image, x, y) + love.graphics.setColor(r, g, b, a) end local function install(std, game, application) application.callbacks.draw = application.callbacks.draw or function() end + -- pure love std.draw.color=color std.draw.rect=rect std.draw.text=text std.draw.line=line - std.draw.font=font + + -- engine dependent + std.draw.image=function(src, x, y) return image(std, src, x, y) end + std.draw.font=function(name, size) return font(std, name, size) end std.draw.clear = function(c) color(c) diff --git a/src/engine/core/love/main.lua b/src/engine/core/love/main.lua index 2075f9b..bfefe05 100644 --- a/src/engine/core/love/main.lua +++ b/src/engine/core/love/main.lua @@ -6,14 +6,17 @@ local engine_math = require('src/lib/engine/math') local engine_draw = require('src/engine/core/love/draw') local engine_keys = require('src/engine/core/love/keys') local engine_loop = require('src/engine/core/love/loop') +local engine_memory = require('src/lib/engine/memory') local engine_color = require('src/lib/object/color') local engine_http = require('src/lib/engine/http') +local engine_i18n = require('src/lib/engine/i18n') local engine_encoder = require('src/lib/engine/encoder') local engine_draw_fps = require('src/lib/draw/fps') local engine_draw_poly = require('src/lib/draw/poly') local protocol_curl_love = require('src/lib/protocol/http_curl_love') local library_csv = require('src/third_party/csv/rodrigodornelles') local library_json = require('src/third_party/json/rxi') +local util_lua = require('src/lib/util/lua') local game = require('src/lib/object/game') local std = require('src/lib/object/std') @@ -44,14 +47,16 @@ function love.load(args) :package('@keys', engine_keys) :package('@loop', engine_loop) :package('@color', engine_color) - :package('@draw_fps', engine_draw_fps) - :package('@draw_poly', engine_draw_poly, polygons) + :package('@draw.fps', engine_draw_fps) + :package('@draw.poly', engine_draw_poly, polygons) + :package('@memory', engine_memory) :package('load', zeebo_module.load) :package('math', engine_math.clib) - :package('random', engine_math.clib_random) + :package('math.random', engine_math.clib_random) :package('http', engine_http, protocol_curl_love) :package('csv', engine_encoder, library_csv) :package('json', engine_encoder, library_json) + :package('i18n', engine_i18n, util_lua.get_sys_lang) :register(function(listener) love.update = listener('loop') love.draw = listener('draw') diff --git a/src/engine/core/native/main.lua b/src/engine/core/native/main.lua index c9d567e..8619831 100644 --- a/src/engine/core/native/main.lua +++ b/src/engine/core/native/main.lua @@ -6,6 +6,8 @@ local engine_http = require('src/lib/engine/http') local engine_encoder = require('src/lib/engine/encoder') local engine_draw_fps = require('src/lib/draw/fps') local engine_draw_poly = require('src/lib/draw/poly') +local engine_i18n = require('src/lib/engine/i18n') +local engine_memory = require('src/lib/engine/memory') local library_csv = require('src/third_party/csv/rodrigodornelles') local game = require('src/lib/object/game') local std = require('src/lib/object/std') @@ -42,20 +44,30 @@ end function native_callback_init(width, height, game_lua) application = zeebo_module.loadgame(game_lua) + + std.draw.clear=native_draw_clear + std.draw.color=native_draw_color + std.draw.font=native_draw_font + std.draw.text=native_draw_text + std.draw.rect=native_draw_rect + std.draw.line=native_draw_line + std.draw.image=native_draw_image zeebo_module.require(std, game, application) :package('@game', engine_game) :package('@math', engine_math) :package('@color', engine_color) - :package('@draw_fps', engine_draw_fps) - :package('@draw_poly', engine_draw_poly, native_dict_poly) + :package('@draw.fps', engine_draw_fps) + :package('@draw.poly', engine_draw_poly, native_dict_poly) + :package('@memory', engine_memory) :package('load', zeebo_module.load) :package('math', engine_math.clib) - :package('random', engine_math.clib_random) + :package('math.random', engine_math.clib_random) :package('http', engine_http, native_dict_http) :package('json', engine_encoder, native_dict_json) :package('xml', engine_encoder, native_dict_xml) :package('csv', engine_encoder, library_csv) + :package('i18n', engine_i18n, native_get_system_lang) :register(function(listener) extraevents.loop = listener('loop') extraevents.draw = listener('draw') @@ -63,13 +75,6 @@ function native_callback_init(width, height, game_lua) end) :run() - std.draw.clear=native_draw_clear - std.draw.color=native_draw_color - std.draw.font=native_draw_font - std.draw.text=native_draw_text - std.draw.rect=native_draw_rect - std.draw.line=native_draw_line - game.width = width game.height = height game.fps = 60 diff --git a/src/engine/core/nintendo_wii/main.lua b/src/engine/core/nintendo_wii/main.lua index ceaa9c9..faf5240 100644 --- a/src/engine/core/nintendo_wii/main.lua +++ b/src/engine/core/nintendo_wii/main.lua @@ -31,10 +31,10 @@ function love.load(args) :package('@keys', engine_keys) :package('@loop', engine_loop) :package('@color', engine_color) - :package('@draw_poly', engine_draw_poly, polygons) + :package('@draw.poly', engine_draw_poly, polygons) :package('load', zeebo_module.load) :package('math', engine_math.clib) - :package('random', engine_math.clib_random) + :package('math.random', engine_math.clib_random) :run() game.width, game.height = w, h diff --git a/src/engine/core/repl/main.lua b/src/engine/core/repl/main.lua index 6ab70cb..f8c96fa 100644 --- a/src/engine/core/repl/main.lua +++ b/src/engine/core/repl/main.lua @@ -1,4 +1,3 @@ ---! @file src/lib/repl/main.lua --! @short Read Eval Print Loop --! @brief an interpreter to debuging the game via stdio. --! @par Extended Backus-Naur Form @@ -83,7 +82,7 @@ local function main() :package('@color', engine_color) :package('load', zeebo_module.load) :package('math', engine_math.clib) - :package('random', engine_math.clib_random) + :package('math.random', engine_math.clib_random) :package('http', engine_http, protocol_curl) :package('csv', engine_encoder, library_csv) :package('json', engine_encoder, library_json) diff --git a/src/lib/cli/meta.lua b/src/lib/cli/meta.lua index 41b0da9..9c9228f 100644 --- a/src/lib/cli/meta.lua +++ b/src/lib/cli/meta.lua @@ -1,4 +1,5 @@ local application_default = require('src/lib/object/application') +local zeebo_module = require('src/lib/engine/module') local function replace(src, meta, default) if src and #src > 0 then @@ -65,18 +66,8 @@ local function run(self) return self end -local function current(game, application) - local gamefile = game and io.open(game, 'r') - local bytecode = gamefile and gamefile:read('*a') - local metadata = bytecode and (loadstring and loadstring(bytecode) or load(bytecode)) - - if gamefile then - gamefile:close() - end - - while type(metadata) == 'function' do - metadata = metadata() - end +local function current(gamefile, application) + local metadata = zeebo_module.loadgame(gamefile) if not application then application = { diff --git a/src/lib/draw/fps.lua b/src/lib/draw/fps.lua index 34bd9ed..f676c77 100644 --- a/src/lib/draw/fps.lua +++ b/src/lib/draw/fps.lua @@ -1,3 +1,17 @@ +--! @defgroup std +--! @{ +--! @defgroup draw +--! @{ + +--! @hideparam self +--! @pre the mode 3 require @c math +--! +--! @param show @c integer +--! @li mode 1: FPS +--! @li mode 2: FPS / FPS Limit +--! @li mode 3: FPS Real Time / FPS / FPS Limit +--! @param x @c double +--! @param y @c double local function fps(self, show, x, y) local s = 4 self.std.draw.color(0xFFFF00FF) @@ -27,6 +41,9 @@ local function fps(self, show, x, y) end end +--! @} +--! @} + local function install(std, game, application) std = std or {} std.draw = std.draw or {} diff --git a/src/lib/draw/poly.lua b/src/lib/draw/poly.lua index 913e3c9..932be1d 100644 --- a/src/lib/draw/poly.lua +++ b/src/lib/draw/poly.lua @@ -43,7 +43,14 @@ local function decorator_triangle(func_draw_poly, std, func_draw_triangle) end end -local function decorator_poly(func_draw_poly, std, modes, repeats) +local function decorator_poly(func_draw_poly, std, modes, repeats) + local func_repeat = function(verts, mode) + if repeats and repeats[mode + 1] then + verts[#verts + 1] = verts[1] + verts[#verts + 1] = verts[2] + end + end + return function (engine_mode, verts, x, y, scale, angle, ox, oy) if #verts < 6 or #verts % 2 ~= 0 then return end local mode = modes and modes[engine_mode + 1] or engine_mode @@ -51,11 +58,6 @@ local function decorator_poly(func_draw_poly, std, modes, repeats) ox = ox or 0 oy = oy or ox or 0 - if repeats and repeats[engine_mode + 1] then - verts[#verts + 1] = verts[1] - verts[#verts + 1] = verts[2] - end - if x and y and not rotated then local index = 1 local verts2 = {} @@ -69,6 +71,8 @@ local function decorator_poly(func_draw_poly, std, modes, repeats) end index = index + 1 end + + func_repeat(verts2, engine_mode) func_draw_poly(mode, verts2) elseif x and y then local index = 1 @@ -82,6 +86,8 @@ local function decorator_poly(func_draw_poly, std, modes, repeats) verts2[index + 1] = yy index = index + 2 end + + func_repeat(verts2, engine_mode) func_draw_poly(mode, verts2) else func_draw_poly(mode, verts) diff --git a/src/lib/engine/game.lua b/src/lib/engine/game.lua index 88b8595..bd9ed55 100644 --- a/src/lib/engine/game.lua +++ b/src/lib/engine/game.lua @@ -3,6 +3,7 @@ --! @defgroup game --! @{ +--! @hideparam self local function reset(self) return function() if self.callbacks.exit then @@ -14,6 +15,7 @@ local function reset(self) end end +--! @hideparam self local function exit(self) return function() if self.callbacks.exit then diff --git a/src/lib/engine/hash.lua b/src/lib/engine/hash.lua new file mode 100644 index 0000000..8233aed --- /dev/null +++ b/src/lib/engine/hash.lua @@ -0,0 +1,59 @@ + +--! @defgroup std +--! @{ +--! @defgroup hash +--! @pre require @c hash +--! @{ + +--! @li https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed +--! +--! @return integer 32bit +local function djb2(digest) + local index = 1 + local hash = 5381 + while index <= #digest do + local char = string.byte(digest, index) + hash = (hash * 33) + char + index = index + 1 + end + + hash = string.format('%08x', hash) + hash = tonumber(hash:sub(#hash - 7), 16) + + return hash +end + +--! @hideparam all_your_secrets +--! @return integer 32bit +local function fingerprint(all_your_secrets) + local index = 1 + local digest = '' + while index <= #all_your_secrets do + local value = all_your_secrets[index] + if type(value) == 'function' then + digest = digest..tostring(value()) + else + digest = digest..tostring(value) + end + index = index + 1 + end + return djb2(digest) +end + +--! @} +--! @} + +local function install(std, game, application, all_your_secrets) + local id = fingerprint(all_your_secrets) + std = std or {} + std.hash = std.hash or {} + std.hash.djb2 = djb2 + std.hash.fingerprint = function() return id end + return {hash=std.hash} +end + +local P = { + install = install +} + +return P diff --git a/src/lib/engine/http.lua b/src/lib/engine/http.lua index 828980b..a3064c2 100644 --- a/src/lib/engine/http.lua +++ b/src/lib/engine/http.lua @@ -7,17 +7,20 @@ local zeebo_pipeline = require('src/lib/util/pipeline') --! @{ --! @short reduced response +--! @hideparam self --! @brief disconnect when receiving status local function fast(self) self.speed = '_fast' return self end +--! @hideparam self local function body(self, content) self.body_content=content return self end +--! @hideparam self local function param(self, name, value) local index = #self.param_list + 1 self.param_list[index] = name @@ -25,6 +28,7 @@ local function param(self, name, value) return self end +--! @hideparam self local function header(self, name, value) local index = #self.header_list + 1 self.header_list[index] = name @@ -32,16 +36,19 @@ local function header(self, name, value) return self end +--! @hideparam self local function success(self, handler_func) self.success_handler = handler_func return self end +--! @hideparam self local function failed(self, handler_func) self.failed_handler = handler_func return self end +--! @hideparam self local function error(self, handler_func) self.error_handler = handler_func return self diff --git a/src/lib/engine/i18n.lua b/src/lib/engine/i18n.lua new file mode 100644 index 0000000..001c6f5 --- /dev/null +++ b/src/lib/engine/i18n.lua @@ -0,0 +1,183 @@ +local language = 'en-US' +local language_default = 'en-US' + +local language_list = {} +local language_inverse_list = {} + +local translate = {} + +local function update_languages(texts) + local index = 1 + + translate = texts + language_list = {language_default} + language_inverse_list = {[language_default]=1} + + repeat + local lang = next(texts) + if lang then + index = index + 1 + language_inverse_list[lang] = index + language_list[#language_list + 1] = lang + end + until lang +end + +--! @defgroup std +--! @{ +--! @defgroup i18n +--! @short Internationalization +--! @brief support multi-language games. +--! @pre require @c i18n +--! @details +--! The format of language must be @c aa-AA, respectively ISO 639 and ISO 3166, +--! This module is based on the system language, +--! but can also store the last saved language. +--! +--! @par Example +--! @code +--! local function i18n(std, game) +--! return { +--! ['pt-BR'] = { +--! ['hello world'] = 'ola mundo' +--! }, +--! ['es-ES'] = { +--! ['hello world'] = 'hola mundo' +--! } +--! } +--! end +--! +--! local function draw(std, game) +--! std.draw.clear(std.color.black) +--! std.draw.color(std.color.white) +--! std.draw.text(8, 8, 'hello world') +--! end +--! +--! local P = { +--! meta = { +--! title='Hello' +--! }, +--! config = { +--! require='i18n' +--! }, +--! callbacks = { +--! i18n=i18n, +--! draw=draw +--! } +--! } +--! +--! return P +--! @endcode +--! @{ + +--! @par Example +--! @code +--! local some_text = std.i18n.get_text('some-text') +--! std.draw.text(8, 8, rot32(some_text)) +--! @endcode +local function get_text(old_text) + local new_text = translate[language] and translate[language][old_text] + return new_text or old_text +end + +--! @par Example +--! @code +--! local language = std.i18n.get_language() +--! std.draw.text(8, 8, language) +--! @endcode +local function get_language() + return language +end + +--! @par Example +--! @code +--! std.i18n.set_langauge('en-US') +--! @endcode +local function set_language(l) + if language_inverse_list[l] then + language = l + else + language = language_default + end +end + +--! @par Example +--! @code +--! if game.state == game.menu_lang and std.key.press.left then +--! std.i18n.back_language() +--! end +--! @endcode +local function back_language() + local index = language_inverse_list[language] + if index <= 1 then + index = #language_list + 1 + end + index = index - 1 + set_language(language_list[index]) +end + +--! @par Example +--! @code +--! if game.state == game.menu_lang and std.key.press.right then +--! std.i18n.next_language() +--! end +--! @endcode +local function next_language() + local index = language_inverse_list[language] + if index >= #language_list then + index = 0 + end + index = index + 1 + set_language(language_list[index]) +end + +--! @} +--! @} + +local function decorator_draw_text(func) + return function (x, y, text) + if text then + text = get_text(text) + else + x = get_text(x) + end + return func(x, y, text) + end +end + +local function install(std, game, application, system_language) + if not (std and std.draw and std.draw.text) then + error('missing draw text') + end + + local old_draw_text = std.draw.text + local texts = application.callbacks.i18n(std, game) + update_languages(texts) + + if system_language then + set_language(system_language()) + end + + std.draw.text = decorator_draw_text(old_draw_text) + std.i18n = {} + std.i18n.get_text = get_text + std.i18n.get_language = get_language + std.i18n.set_language = set_language + std.i18n.back_language = back_language + std.i18n.next_language = next_language + + return { + std={ + i18n=std.i18n, + draw={ + text=std.draw.text + } + } + } +end + +local P = { + install=install +} + +return P diff --git a/src/lib/engine/memory.lua b/src/lib/engine/memory.lua new file mode 100644 index 0000000..266fa4e --- /dev/null +++ b/src/lib/engine/memory.lua @@ -0,0 +1,107 @@ +local memory_dict_unload = {} +local memory_dict = {} +local memory_list = {} + +--! @defgroup std +--! @{ +--! @defgroup mem +--! @{ +--! @short Memory Manager +--! @brief A Garbage Collector System and also mangament manual of memory. + +--! @par Example +--! @code +--! local function slow_function() +--! return 1 + 1 +--! end +--! +--! local function loop(std, game) +--! if std.key.press.a then +--! local result = std.mem.cache('slow-function', slow_function) +--! std.draw.text(result) +--! end +--! end +--! @endcode +local function cache(key, load_func, unload_func) + if memory_dict[key] then + return memory_dict[key] + end + if not load_func then + return nil + end + + local value = load_func() + memory_list[#memory_list + 1] = key + memory_dict_unload[key] = unload_func + memory_dict[key] = value + return value +end + +--! @par Example +--! @code +--! local function slow_function() +--! return 1 + 1 +--! end +--! +--! local function loop(std, game) +--! if std.key.press.b then +--! std.mem.unset('slow-function') +--! end +--! end +--! @endcode +local function unset(key) + if memory_dict_unload[key] then + memory_dict_unload[key](memory_dict[key]) + end + memory_dict[key] = nil +end + +--! @warning Do not use it frequently as @c loop() or @c draw(), +--! but at strategic points such as changing games or world. +--! This will clear all allocated memory, such as images, fonts, audios and things defined by you. +--! @par Example +--! @code +--! local function load_game(url) +--! std.http.get(url) +--! :success(function(std, game) +--! std.mem.gc_clear_all() +--! game.application = std.game.load(std.http.body) +--! game.application.init(std, game) +--! end) +--! :run() +--! end +--! @endcode +local function gc_clear_all() + local index = 1 + local items = #memory_list + + while index <= items do + unset(memory_list[index]) + index = index + 1 + end + + memory_list = {} + + return items +end + +--! @} +--! @} + +local function install(std, game, application) + std = std or {} + std.mem = std.mem or {} + std.mem.cache = cache + std.mem.unset = unset + std.mem.gc_clear_all = gc_clear_all + + return { + mem=std.mem + } +end + +local P = { + install=install +} + +return P diff --git a/src/lib/engine/module.lua b/src/lib/engine/module.lua index 6e71e4c..2bbe3d4 100644 --- a/src/lib/engine/module.lua +++ b/src/lib/engine/module.lua @@ -1,13 +1,55 @@ local zeebo_pipeline = require('src/lib/util/pipeline') +local application_default = require('src/lib/object/application') +local function normalize(application) + if not application then return nil end + + if application.Game then + application = application.Game + end + + if application.new and type(application.new) == 'function' then + application = application.new() + end + + if application and application.meta and application.callbacks then + return application + end + + local normalized_aplication = { + meta = {}, + config = {}, + callbacks = {} + } + + for key, value in pairs(application) do + if application_default.meta[key] then + normalized_aplication.meta[key] = value + elseif type(value) == 'function' then + normalized_aplication.callbacks[key] = value + else + normalized_aplication.config[key] = value + end + end + + return normalized_aplication +end + +--! @defgroup std +--! @{ +--! @defgroup game +--! @{ + +--! @renamefunc load --! @short safe load game +--! @pre require @c load --! @brief search by game in filesystem / lua modules --! @li https://love2d.org/wiki/love.filesystem.getSource local function loadgame(game_file) - if type(game_file) == 'table' then - return game_file + if type(game_file) == 'table' or type(game_file) == 'userdata' then + return normalize(game_file) end - + local cwd = '.' local application = type(game_file) == 'function' and game_file local game_title = game_file and game_file:gsub('%.lua$', '') or 'game' @@ -31,7 +73,7 @@ local function loadgame(game_file) application = ok and app end end - if not application and io and io.open then + if not application and io and io.open and game_file then local app_file = io.open(game_file) if app_file then local app_src = app_file:read('*a') @@ -48,9 +90,12 @@ local function loadgame(game_file) application = application() end - return application + return normalize(application) end +--! @} +--! @} + local function register(self, register_func) local listener_func = function(event_name) local filtered_events = {} diff --git a/src/lib/object/application.lua b/src/lib/object/application.lua index c8a2bb6..791fdad 100644 --- a/src/lib/object/application.lua +++ b/src/lib/object/application.lua @@ -1,4 +1,3 @@ ---! @file src/lib/object/application.lua --! @short application object --! @brief metatags, configs and code. diff --git a/src/lib/object/game.lua b/src/lib/object/game.lua index 47d3640..a57fb97 100644 --- a/src/lib/object/game.lua +++ b/src/lib/object/game.lua @@ -1,4 +1,3 @@ ---! @file src/lib/object/game.lua --! @short game object --! @brief a table to put anything related to developer game. diff --git a/src/lib/object/std.lua b/src/lib/object/std.lua index c580b59..c6bdd2a 100644 --- a/src/lib/object/std.lua +++ b/src/lib/object/std.lua @@ -1,4 +1,3 @@ ---! @file src/lib/object/std.lua --! @short standard library object --! @brief can be used as mock @@ -7,6 +6,7 @@ local P = { }, draw = { + image = function() end, clear = function () end, color = function () end, rect = function () end, diff --git a/src/lib/protocol/http_ginga.lua b/src/lib/protocol/http_ginga.lua index c2a31e4..edb08b9 100644 --- a/src/lib/protocol/http_ginga.lua +++ b/src/lib/protocol/http_ginga.lua @@ -1,4 +1,3 @@ ---! @file src/lib/protocol/http_ginga.lua --! @short HTTP ginga module --! --! @li @b specification: https://www.rfc-editor.org/rfc/rfc2616 diff --git a/src/lib/util/lua.lua b/src/lib/util/lua.lua index 3d7d830..94272da 100644 --- a/src/lib/util/lua.lua +++ b/src/lib/util/lua.lua @@ -1,3 +1,5 @@ +local os = require('os') + local function has_support_utf8() if jit then return true @@ -10,8 +12,27 @@ local function has_support_utf8() return false end +local function get_sys_lang() + if not os then + return 'en-US' + end + + local lang, contry = (os.setlocale() or ''):match('LC_CTYPE=(%a%a).(%a%a)') + + if not lang then + lang, country = (os.getenv('LANG') or ''):match('(%a%a).(%a%a)') + end + + if not lang then + lang, country = 'en', 'US' + end + + return string.lower(lang)..'-'..string.upper(country) +end + local P = { - has_support_utf8=has_support_utf8 + has_support_utf8=has_support_utf8, + get_sys_lang=get_sys_lang } return P diff --git a/tests/test_lib_engine_hash.lua b/tests/test_lib_engine_hash.lua new file mode 100644 index 0000000..9eca9cf --- /dev/null +++ b/tests/test_lib_engine_hash.lua @@ -0,0 +1,25 @@ +local luaunit = require('luaunit') +local engine_hash = require('src/lib/engine/hash') + +local std = engine_hash.install(nil, nil, nil, {'awesome', function() return 42 end}) + +function test_fingerprint() + local expected = std.hash.djb2('awesome42') + local result = std.hash.fingerprint() + luaunit.assertEquals(expected, result) +end + +function test_diff_hash_foo_bar() + local foo = std.hash.djb2('foo') + local bar = std.hash.djb2('bar') + luaunit.assertNotEquals(foo, bar) +end + +function test_collision_stylist_subgenera() + local stylist = std.hash.djb2('stylist') + local subgenera = std.hash.djb2('subgenera') + luaunit.assertEquals(stylist, subgenera) +end + + +os.exit(luaunit.LuaUnit.run()) diff --git a/tools/build_npm_cli.lua b/tools/build_npm_cli.lua deleted file mode 100644 index 7d167b5..0000000 --- a/tools/build_npm_cli.lua +++ /dev/null @@ -1,11 +0,0 @@ -local os = require('os') - -assert(os.execute('npm install -g demoon > /dev/null')) -assert(os.execute('rm -Rf ./dist')) -assert(os.execute('./cli.sh cli-build')) -assert(os.execute('mkdir -p dist/bin/')) -assert(os.execute('npx demoon ./dist/cli.lua compiler ./dist/cli.lua --dist cli.out')) -assert(os.execute('echo "#!/usr/bin/env -S npx demoon" > header.txt')) -assert(os.execute('cat header.txt cli.out > dist/bin/gly-cli')) -assert(os.execute('rm dist/cli.lua header.txt cli.out')) -assert(os.execute('cp npm-gly-cli.json dist/package.json')) diff --git a/tools/cd_npm_core-native-html5.lua b/tools/cd_npm_core-native-html5.lua new file mode 100644 index 0000000..47dda4c --- /dev/null +++ b/tools/cd_npm_core-native-html5.lua @@ -0,0 +1,8 @@ +local cmd = function(c) assert(require('os').execute(c), c) end +local version = io.open('src/cli/commands/info.lua'):read('*a'):match('(%d+%.%d+%.%d+)') + +cmd('rm -Rf ./dist') +cmd('mkdir -p ./dist/dist') +cmd('cp ./npm/core-native-html5/README.md ./dist/README.md') +cmd('cp ./src/engine/core/html5/core-native-html5.js ./dist/dist/index.js') +cmd('./cli.sh fs-replace npm/core-native-html5/package.json ./dist/package.json --format "{{version}}" --replace '..version) diff --git a/tools/cd_npm_gly-cli.lua b/tools/cd_npm_gly-cli.lua new file mode 100644 index 0000000..088ac89 --- /dev/null +++ b/tools/cd_npm_gly-cli.lua @@ -0,0 +1,13 @@ +local cmd = function(c) assert(require('os').execute(c), c) end +local version = io.open('src/cli/commands/info.lua'):read('*a'):match('(%d+%.%d+%.%d+)') + +cmd('npm install -g demoon > /dev/null 2>/dev/null') +cmd('rm -Rf ./dist') +cmd('./cli.sh cli-build') +cmd('mkdir -p ./dist/bin') +cmd('npx demoon ./dist/cli.lua compiler ./dist/cli.lua --dist ./dist/cli.out') +cmd('echo "#!/usr/bin/env -S npx demoon" > ./dist/header.txt') +cmd('cat ./dist/header.txt ./dist/cli.out > dist/bin/gly-cli') +cmd('rm ./dist/cli.lua ./dist/header.txt ./dist/cli.out') +cmd('./cli.sh fs-replace npm/gly-cli/package.json ./dist/package.json --format "{{version}}" --replace '..version) +cmd('./cli.sh fs-replace README.md ./dist/README.md --format "lua cli.lua" --replace "npx gly-cli"') diff --git a/tools/cd_npm_gly-engine.lua b/tools/cd_npm_gly-engine.lua new file mode 100644 index 0000000..8c1ea2e --- /dev/null +++ b/tools/cd_npm_gly-engine.lua @@ -0,0 +1,9 @@ +local cmd = function(c) assert(require('os').execute(c), c) end +local version = io.open('src/cli/commands/info.lua'):read('*a'):match('(%d+%.%d+%.%d+)') + +cmd('rm -Rf ./dist') +cmd('./cli.sh build --core native --bundler') +cmd('mkdir -p ./dist/dist') +cmd('mv ./dist/main.lua ./dist/dist/main.lua') +cmd('cp ./npm/gly-engine/README.md ./dist/README.md') +cmd('./cli.sh fs-replace npm/gly-engine/package.json ./dist/package.json --format "{{version}}" --replace '..version) diff --git a/tools/doxygen_filter.lua b/tools/doxygen_filter.lua new file mode 100644 index 0000000..1e6c05e --- /dev/null +++ b/tools/doxygen_filter.lua @@ -0,0 +1,179 @@ +function support() + local file = io.open('README.md') + local content = file:read('*a') + local begin_pos = content:find('#### CLI Platform Support') + local end_pos = content:find('\n%-%-%-\n') + content = content:sub(begin_pos, end_pos) + content = content:gsub(':ok:', 'yes') + content = content:gsub(':x:', 'no') + file:close() + return content +end + +function commands() + local name = 'src/cli/main.lua' + local file = io.open(name) + local src = file:read('*a') + local content = '' + local next_cmd = src:gmatch('%.add_subcommand%(\'([%w%-]+)\'') + repeat + local cmd = next_cmd() + if cmd then + local pid = io.popen('lua '..name..' help '..cmd) + local usage = pid:read('*a') + local tutorial = usage:gsub('usage: ', ''):gsub(name, 'gly-cli') + content = content..'//! @code{.sql}\n//! '..tutorial..'//! @endcode\n' + pid:close() + end + until not cmd + file:close() + return content +end + +local function source(file_name) + local content = '' + local file = io.open(file_name, 'r') + repeat + local line = file:read('*l') + if line and not line:find('^%-%-%!') then + content = content..line..'\n' + end + until not line + file:close() + return content +end + +local function group(a, b, c) + return '//! @defgroup '..a..'\n//! @{\n//! @defgroup '..b..' '..c..'\n//! @{\n' +end + +local function game_screenshot(game) + return '//! @par Screenshot \n//! @li not avaliable\n' +end + +local function game_requires(game) + local content = '' + local libraries = game.config and game.config.require or '' + local next_library = libraries:gmatch('%S+') + repeat + local library = next_library() + if library then + content = content..' @c '..library + end + until not library + if #content > 0 then + content = '//! @pre require '..content..'\n' + end + return content +end + +function main() + local file = io.open(arg[1], 'r') + if not file then + return + end + + local is_txt = arg[1]:sub(#arg[1] - 3) == '.txt' + local is_lua = arg[1]:sub(#arg[1] - 3) == '.lua' + local is_game = arg[1]:sub(#arg[1] - 7) == 'game.lua' and arg[1]:find('examples') + local renamefunc_pattern = '@renamefunc ([%w_]+)' + local hideparam_pattern = '@hideparam ([%w_]+)' + local include_pattern = '^local [%w_%-]+ = require%(\'(.-)\'%)' + local function_pattern = '^local function ([%w_]+%b())' + local literal_pattern = '^local ([%w_%-]+) = ([%d%w\'"-_]+)' + local command_pattern = '@call (%w+)' + local doxygen_pattern = '%-%-%!' + + if is_txt then + io.write('/**\n') + end + + if is_lua then + io.write('//! @file '..arg[1]..'\n') + end + + if is_game then + local game = dofile(arg[1]) + local game_name = arg[1]:match('(%w+)/game.lua$') + io.write(group('Examples', game_name, game.meta.title)) + io.write(game_requires(game)) + io.write('//! @author '..game.meta.author..'\n') + io.write('//! @version '..game.meta.version..'\n') + io.write('//! @par Brief \n//! @details '..game.meta.description..'\n') + io.write(game_screenshot(game)) + game_src = source(arg[1]) + end + + local rename_function = false + local params_hiden = {} + + repeat + local line = file:read('*l') + + if line then + local breakline = true + local command = line:match(command_pattern) + local doxygen = line:match(doxygen_pattern) + local include = line:match(include_pattern) + local clojure = line:match(function_pattern) + local hideparam = line:match(hideparam_pattern) + local rename_func = line:match(renamefunc_pattern) + local variable, literal = line:match(literal_pattern) + + if is_lua and doxygen then + line = line:gsub(doxygen_pattern, '//!') + end + + if rename_function and clojure then + clojure = clojure:gsub('^([%w_]+)', rename_function) + rename_function = false + end + + if #params_hiden > 0 and clojure then + local index = 1 + while index <= #params_hiden do + clojure = clojure:gsub(params_hiden[index]..'[,]?', '') + index = index + 1 + end + params_hiden = {} + end + + if rename_func then + rename_function = rename_func + elseif hideparam then + params_hiden[#params_hiden + 1] = hideparam + elseif include then + io.write('#include "'..include..'.lua"') + elseif is_game and not doxygen then + breakline = false + elseif clojure then + io.write('local function-'..clojure..';') + elseif variable and literal then + io.write('local '..variable..' = '..literal..';') + elseif command and _G[command] then + io.write(_G[command](line)) + elseif doxygen then + io.write(line) + elseif is_txt then + io.write(line) + elseif line:find('%S') then + breakline = false + end + + if breakline then + io.write('\n') + end + end + until not line + + if is_txt then + io.write(' */\n') + end + + if is_game then + io.write('//! @par Source Code \n//! @code{.java}\n'..source(arg[1])..'\n//! @endcode\n') + io.write('//! @} @}\n') + end +end + +main() diff --git a/tools/doxygen_plugins.lua b/tools/doxygen_plugins.lua new file mode 100644 index 0000000..c772afa --- /dev/null +++ b/tools/doxygen_plugins.lua @@ -0,0 +1,23 @@ +local cmd = function(c) assert(require('os').execute(c), c) end + +cmd('mkdir -p doxygen') +cmd('doxygen -w html doxygen/header.html doxygen/delete_me.html doxygen/delete_me.css') + +local text_clipboard = '\n' +local copy_clipboard_pattern = '<%!%-%-END COPY_CLIPBOARD%-%->' +local plugin_toggle_darkmode = '\n' + ..'' +local plugin_paragraph_link = '\n' + ..'' + +local header_file = io.open('doxygen/header.html', 'r') +local header_content = header_file:read('*a') + +header_file:close() + +header_file = io.open('doxygen/header.html', 'w') +header_content = header_content:gsub(copy_clipboard_pattern, text_clipboard..plugin_paragraph_link) +header_content = header_content:gsub(copy_clipboard_pattern, text_clipboard..plugin_toggle_darkmode) + +header_file:write(header_content) +header_file:close() diff --git a/tools/versionchecker.lua b/tools/versionchecker.lua index 8f3f77a..83f6cf0 100644 --- a/tools/versionchecker.lua +++ b/tools/versionchecker.lua @@ -2,17 +2,11 @@ local cli_file = io.open('src/cli/commands/info.lua') local cli_text = cli_file and cli_file:read('*a') local cli_version = cli_text and cli_text:match('(%d+%.%d+%.%d+)') -local npm_file = io.open('npm-gly-cli.json') -local npm_text = npm_file and npm_file:read('*a') -local npm_version = npm_text and npm_text:match('(%d+%.%d+%.%d+)') - local doxygen_file = io.open('Doxyfile') local doxygen_text = doxygen_file and doxygen_file:read('*a') local doxygen_version = doxygen_text and doxygen_text:match('(%d+%.%d+%.%d+)') print('cli.lua:', cli_version) -print('npm.js: ', npm_version) print('doxygen:', doxygen_version) -assert(cli_version == npm_version) assert(cli_version == doxygen_version)