diff --git a/.github/workflows/ep13-lsp.yml b/.github/workflows/ep13-lsp.yml index eb08d61..743da7d 100644 --- a/.github/workflows/ep13-lsp.yml +++ b/.github/workflows/ep13-lsp.yml @@ -14,13 +14,14 @@ jobs: with: python-version: "3.11" - uses: abatilo/actions-poetry@v2 - - name: Install dependencies - run: poetry install --no-root --with dev + - name: Install dependencies and build Cython extension + run: poetry install --no-root + - name: Rebuild the project + run: poetry build - name: Cache venv created by poetry (configured to be in '.venv') uses: actions/cache@v3 with: path: ./.venv key: venv-${{ runner.os }}-${{ hashFiles('poetry.lock') }} - name: Run pyright - run: | - poetry run pyright + run: poetry run pyright diff --git a/.gitignore b/.gitignore index a645c13..157ffea 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,12 @@ .vscode *.ogg *.sw[nop] +*.so +*.c +*.so +*.html +*.cache +dist +build +*.egg-info +*.prof diff --git a/episode-13/README.md b/episode-13/README.md index b5b914b..7ec05c4 100644 --- a/episode-13/README.md +++ b/episode-13/README.md @@ -7,4 +7,27 @@ Clock on the thumbnail below to watch the video: This episode is split into two parts: - EP13a: The code has gotten a little crusty. This episode restructures the codebase, adds a `pyproject.toml` file and switches to Poetry for better dependency management, and adds a formatter and a linter as well as a CI setup to check all this. -- EP13b: Loading big save files took a long-ass time before this. This episode covers profiling and optimization techniques and rewrites a lot of the chunk loading code in Cython to speed it up dramatically. +- EP13b: Loading big save files took a long-ass time before this. This episode covers profiling and optimization techniques and rewrites a lot of the chunk loading code in Cython to speed it up dramatically. It also covers some frame time improvements in preparation for adding mobs. + +## Installing dependencies + +Using [Poetry](https://python-poetry.org/): + +```console +poetry install +``` + +This will also build the Cython extensions. +If you ever need to rebuild them, you can do: + +```console +poetry build +``` + +## Running + +Again, using Poetry: + +```console +poetry run python mcpy.py +``` diff --git a/episode-13/build.py b/episode-13/build.py new file mode 100644 index 0000000..0610fb3 --- /dev/null +++ b/episode-13/build.py @@ -0,0 +1,28 @@ +from setuptools.command.build_ext import build_ext +from Cython.Build import cythonize +import Cython.Compiler.Options + +Cython.Compiler.Options.cimport_from_pyx = True # needed? + + +class BuildExt(build_ext): + def build_extension(self, ext): + self.inplace = True # Important or the LSP won't have access to the compiled files. + super().build_extension(ext) + + +def build(setup_kwargs): + ext_modules = cythonize( + [ + "src/chunk/__init__.pyx", + "src/chunk/chunk.pyx", + "src/chunk/subchunk.pyx", + ], + compiler_directives={ + "language_level": 3, + "profile": True, + }, + annotate=True, + ) + + setup_kwargs.update({"ext_modules": ext_modules, "cmdclass": {"build_ext": BuildExt}}) diff --git a/episode-13/main.py b/episode-13/mcpy.py similarity index 82% rename from episode-13/main.py rename to episode-13/mcpy.py index 3503386..4f3bd9e 100644 --- a/episode-13/main.py +++ b/episode-13/mcpy.py @@ -1,5 +1,6 @@ import math import random +from cProfile import Profile import pyglet pyglet.options["shadow_window"] = False @@ -50,6 +51,32 @@ def update(self, delta_time): self.player.update(delta_time) + # Load the closest chunk which hasn't been loaded yet. + + x, y, z = self.player.position + closest_chunk = None + min_distance = math.inf + + for chunk_pos, chunk in self.world.chunks.items(): + if chunk.loaded: + continue + + cx, cy, cz = chunk_pos + + cx *= CHUNK_WIDTH + cy *= CHUNK_HEIGHT + cz *= CHUNK_LENGTH + + dist = (cx - x) ** 2 + (cy - y) ** 2 + (cz - z) ** 2 + + if dist < min_distance: + min_distance = dist + closest_chunk = chunk + + if closest_chunk is not None: + closest_chunk.update_subchunk_meshes() + closest_chunk.update_mesh() + def on_draw(self): self.player.update_matrices() @@ -220,6 +247,38 @@ def run(self): pyglet.app.run() +def sample_initial_loading_time(): + with Profile() as profiler: + Game() + profiler.create_stats() + profiler.dump_stats("stats.prof") + + for k in profiler.stats.keys(): + # file line name + file, _, name = k + + if "world.py" in file and name == "__init__": + # ? ncalls time cumtime parent + _, _, _, cumtime, _ = profiler.stats[k] + break + + else: + raise Exception("Couldn't find work init stats!") + + return cumtime + + +def benchmark_initial_loading_time(): + n = 10 + samples = [sample_initial_loading_time() for _ in range(n)] + mean = sum(samples) / n + + print(mean) + exit() + + if __name__ == "__main__": + # benchmark_initial_loading_time() + game = Game() game.run() diff --git a/episode-13/poetry.lock b/episode-13/poetry.lock index 25f0f8a..59e415d 100644 --- a/episode-13/poetry.lock +++ b/episode-13/poetry.lock @@ -11,6 +11,81 @@ files = [ {file = "base36-0.1.1.tar.gz", hash = "sha256:6f221783c5499bd5fd4a1102054df9638d6232ff5ca850c21fd1efe5070c1a96"}, ] +[[package]] +name = "cython" +version = "3.0.11" +description = "The Cython compiler for writing C extensions in the Python language." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +files = [ + {file = "Cython-3.0.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:44292aae17524abb4b70a25111fe7dec1a0ad718711d47e3786a211d5408fdaa"}, + {file = "Cython-3.0.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75d45fbc20651c1b72e4111149fed3b33d270b0a4fb78328c54d965f28d55e1"}, + {file = "Cython-3.0.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d89a82937ce4037f092e9848a7bbcc65bc8e9fc9aef2bb74f5c15e7d21a73080"}, + {file = "Cython-3.0.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea2e7e2d3bc0d8630dafe6c4a5a89485598ff8a61885b74f8ed882597efd5"}, + {file = "Cython-3.0.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cee29846471ce60226b18e931d8c1c66a158db94853e3e79bc2da9bd22345008"}, + {file = "Cython-3.0.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eeb6860b0f4bfa402de8929833fe5370fa34069c7ebacb2d543cb017f21fb891"}, + {file = "Cython-3.0.11-cp310-cp310-win32.whl", hash = "sha256:3699391125ab344d8d25438074d1097d9ba0fb674d0320599316cfe7cf5f002a"}, + {file = "Cython-3.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:d02f4ebe15aac7cdacce1a628e556c1983f26d140fd2e0ac5e0a090e605a2d38"}, + {file = "Cython-3.0.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75ba1c70b6deeaffbac123856b8d35f253da13552207aa969078611c197377e4"}, + {file = "Cython-3.0.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af91497dc098718e634d6ec8f91b182aea6bb3690f333fc9a7777bc70abe8810"}, + {file = "Cython-3.0.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3999fb52d3328a6a5e8c63122b0a8bd110dfcdb98dda585a3def1426b991cba7"}, + {file = "Cython-3.0.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d566a4e09b8979be8ab9f843bac0dd216c81f5e5f45661a9b25cd162ed80508c"}, + {file = "Cython-3.0.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:46aec30f217bdf096175a1a639203d44ac73a36fe7fa3dd06bd012e8f39eca0f"}, + {file = "Cython-3.0.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ddd1fe25af330f4e003421636746a546474e4ccd8f239f55d2898d80983d20ed"}, + {file = "Cython-3.0.11-cp311-cp311-win32.whl", hash = "sha256:221de0b48bf387f209003508e602ce839a80463522fc6f583ad3c8d5c890d2c1"}, + {file = "Cython-3.0.11-cp311-cp311-win_amd64.whl", hash = "sha256:3ff8ac1f0ecd4f505db4ab051e58e4531f5d098b6ac03b91c3b902e8d10c67b3"}, + {file = "Cython-3.0.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:11996c40c32abf843ba652a6d53cb15944c88d91f91fc4e6f0028f5df8a8f8a1"}, + {file = "Cython-3.0.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f2c892e9f9c1698ecfee78205541623eb31cd3a1b682668be7ac12de94aa8e"}, + {file = "Cython-3.0.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b14c24f1dc4c4c9d997cca8d1b7fb01187a218aab932328247dcf5694a10102"}, + {file = "Cython-3.0.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8eed5c015685106db15dd103fd040948ddca9197b1dd02222711815ea782a27"}, + {file = "Cython-3.0.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780f89c95b8aec1e403005b3bf2f0a2afa060b3eba168c86830f079339adad89"}, + {file = "Cython-3.0.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a690f2ff460682ea985e8d38ec541be97e0977fa0544aadc21efc116ff8d7579"}, + {file = "Cython-3.0.11-cp312-cp312-win32.whl", hash = "sha256:2252b5aa57621848e310fe7fa6f7dce5f73aa452884a183d201a8bcebfa05a00"}, + {file = "Cython-3.0.11-cp312-cp312-win_amd64.whl", hash = "sha256:da394654c6da15c1d37f0b7ec5afd325c69a15ceafee2afba14b67a5df8a82c8"}, + {file = "Cython-3.0.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4341d6a64d47112884e0bcf31e6c075268220ee4cd02223047182d4dda94d637"}, + {file = "Cython-3.0.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351955559b37e6c98b48aecb178894c311be9d731b297782f2b78d111f0c9015"}, + {file = "Cython-3.0.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c02361af9bfa10ff1ccf967fc75159e56b1c8093caf565739ed77a559c1f29f"}, + {file = "Cython-3.0.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6823aef13669a32caf18bbb036de56065c485d9f558551a9b55061acf9c4c27f"}, + {file = "Cython-3.0.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6fb68cef33684f8cc97987bee6ae919eee7e18ee6a3ad7ed9516b8386ef95ae6"}, + {file = "Cython-3.0.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:790263b74432cb997740d73665f4d8d00b9cd1cecbdd981d93591ddf993d4f12"}, + {file = "Cython-3.0.11-cp313-cp313-win32.whl", hash = "sha256:e6dd395d1a704e34a9fac00b25f0036dce6654c6b898be6f872ac2bb4f2eda48"}, + {file = "Cython-3.0.11-cp313-cp313-win_amd64.whl", hash = "sha256:52186101d51497519e99b60d955fd5cb3bf747c67f00d742e70ab913f1e42d31"}, + {file = "Cython-3.0.11-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c69d5cad51388522b98a99b4be1b77316de85b0c0523fa865e0ea58bbb622e0a"}, + {file = "Cython-3.0.11-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8acdc87e9009110adbceb7569765eb0980129055cc954c62f99fe9f094c9505e"}, + {file = "Cython-3.0.11-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd47865f4c0a224da73acf83d113f93488d17624e2457dce1753acdfb1cc40c"}, + {file = "Cython-3.0.11-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:301bde949b4f312a1c70e214b0c3bc51a3f955d466010d2f68eb042df36447b0"}, + {file = "Cython-3.0.11-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:f3953d2f504176f929862e5579cfc421860c33e9707f585d70d24e1096accdf7"}, + {file = "Cython-3.0.11-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:3f2b062f6df67e8a56c75e500ca330cf62c85ac26dd7fd006f07ef0f83aebfa3"}, + {file = "Cython-3.0.11-cp36-cp36m-win32.whl", hash = "sha256:c3d68751668c66c7a140b6023dba5d5d507f72063407bb609d3a5b0f3b8dfbe4"}, + {file = "Cython-3.0.11-cp36-cp36m-win_amd64.whl", hash = "sha256:bcd29945fafd12484cf37b1d84f12f0e7a33ba3eac5836531c6bd5283a6b3a0c"}, + {file = "Cython-3.0.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4e9a8d92978b15a0c7ca7f98447c6c578dc8923a0941d9d172d0b077cb69c576"}, + {file = "Cython-3.0.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:421017466e9260aca86823974e26e158e6358622f27c0f4da9c682f3b6d2e624"}, + {file = "Cython-3.0.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80a7232938d523c1a12f6b1794ab5efb1ae77ad3fde79de4bb558d8ab261619"}, + {file = "Cython-3.0.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfa550d9ae39e827a6e7198076df763571cb53397084974a6948af558355e028"}, + {file = "Cython-3.0.11-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:aedceb6090a60854b31bf9571dc55f642a3fa5b91f11b62bcef167c52cac93d8"}, + {file = "Cython-3.0.11-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:473d35681d9f93ce380e6a7c8feb2d65fc6333bd7117fbc62989e404e241dbb0"}, + {file = "Cython-3.0.11-cp37-cp37m-win32.whl", hash = "sha256:3379c6521e25aa6cd7703bb7d635eaca75c0f9c7f1b0fdd6dd15a03bfac5f68d"}, + {file = "Cython-3.0.11-cp37-cp37m-win_amd64.whl", hash = "sha256:14701edb3107a5d9305a82d9d646c4f28bfecbba74b26cc1ee2f4be08f602057"}, + {file = "Cython-3.0.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598699165cfa7c6d69513ee1bffc9e1fdd63b00b624409174c388538aa217975"}, + {file = "Cython-3.0.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0583076c4152b417a3a8a5d81ec02f58c09b67d3f22d5857e64c8734ceada8c"}, + {file = "Cython-3.0.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52205347e916dd65d2400b977df4c697390c3aae0e96275a438cc4ae85dadc08"}, + {file = "Cython-3.0.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:989899a85f0d9a57cebb508bd1f194cb52f0e3f7e22ac259f33d148d6422375c"}, + {file = "Cython-3.0.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53b6072a89049a991d07f42060f65398448365c59c9cb515c5925b9bdc9d71f8"}, + {file = "Cython-3.0.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f988f7f8164a6079c705c39e2d75dbe9967e3dacafe041420d9af7b9ee424162"}, + {file = "Cython-3.0.11-cp38-cp38-win32.whl", hash = "sha256:a1f4cbc70f6b7f0c939522118820e708e0d490edca42d852fa8004ec16780be2"}, + {file = "Cython-3.0.11-cp38-cp38-win_amd64.whl", hash = "sha256:187685e25e037320cae513b8cc4bf9dbc4465c037051aede509cbbf207524de2"}, + {file = "Cython-3.0.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0fc6fdd6fa493be7bdda22355689d5446ac944cd71286f6f44a14b0d67ee3ff5"}, + {file = "Cython-3.0.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b1d1f6f94cc5d42a4591f6d60d616786b9cd15576b112bc92a23131fcf38020"}, + {file = "Cython-3.0.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ab2b92a3e6ed552adbe9350fd2ef3aa0cc7853cf91569f9dbed0c0699bbeab"}, + {file = "Cython-3.0.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:104d6f2f2c827ccc5e9e42c80ef6773a6aa94752fe6bc5b24a4eab4306fb7f07"}, + {file = "Cython-3.0.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:13062ce556a1e98d2821f7a0253b50569fdc98c36efd6653a65b21e3f8bbbf5f"}, + {file = "Cython-3.0.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:525d09b3405534763fa73bd78c8e51ac8264036ce4c16d37dfd1555a7da6d3a7"}, + {file = "Cython-3.0.11-cp39-cp39-win32.whl", hash = "sha256:b8c7e514075696ca0f60c337f9e416e61d7ccbc1aa879a56c39181ed90ec3059"}, + {file = "Cython-3.0.11-cp39-cp39-win_amd64.whl", hash = "sha256:8948802e1f5677a673ea5d22a1e7e273ca5f83e7a452786ca286eebf97cee67c"}, + {file = "Cython-3.0.11-py2.py3-none-any.whl", hash = "sha256:0e25f6425ad4a700d7f77cd468da9161e63658837d1bc34861a9861a4ef6346d"}, + {file = "cython-3.0.11.tar.gz", hash = "sha256:7146dd2af8682b4ca61331851e6aebce9fe5158e75300343f80c07ca80b1faff"}, +] + [[package]] name = "nbtlib" version = "2.0.4" @@ -92,24 +167,113 @@ files = [ [[package]] name = "pyglet" -version = "2.0.16" +version = "2.0.17" description = "pyglet is a cross-platform games and multimedia package." optional = false python-versions = ">=3.8" files = [ - {file = "pyglet-2.0.16-py3-none-any.whl", hash = "sha256:332593c8c14fa2c545a0da3da2f99b8c75c6e822a90cd4ea8e239fad658ff5a1"}, - {file = "pyglet-2.0.16.tar.gz", hash = "sha256:af007b22ff5f302edeb2a06d749cfef53f52e67f1e52f0b2babd840d37193482"}, + {file = "pyglet-2.0.17-py3-none-any.whl", hash = "sha256:c881615a5bf14455af36a0915fd9dad0069da904ab5e0ec19b4d6cdfcf1e84c2"}, + {file = "pyglet-2.0.17.tar.gz", hash = "sha256:50c533c1a7cafdccccf43041338ad921ae26866e9871b4f12bf608500632900a"}, +] + +[[package]] +name = "pyglm" +version = "2.7.1" +description = "OpenGL Mathematics library for Python" +optional = false +python-versions = "*" +files = [ + {file = "PyGLM-2.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:401ccf0ffe22c7513e4102f343e5d93f87c48fb869024b97135ee3a961c38c00"}, + {file = "PyGLM-2.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48967e750329309e93d6b4eddea4b6be21bc195c5c9914b24909090b7dddc2b5"}, + {file = "PyGLM-2.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:311faffc2a05aa269dc0d212bf664e79a231357c8a88b95cb87312b84fe0f2cb"}, + {file = "PyGLM-2.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ae6a8dc299e77e96672c412715ceb7104d8cb162d9a6a0ae1e27699714b3202"}, + {file = "PyGLM-2.7.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a06c90b43a259584e062666c8e28f5959043e09669c84c684fca8380cf4d68b"}, + {file = "PyGLM-2.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff79f9b92135e5a2c0f896ffe0878307ab8c05eb2ef90fc0b4d9a3b63bc03a6c"}, + {file = "PyGLM-2.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87bf940e84d459d694ae23320a0362d2acadc317c032c54a69d761b0746a18dc"}, + {file = "PyGLM-2.7.1-cp310-cp310-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d7065d24c5ea85f9367d94c078399a656f643d056497f2866394077d345ec7e"}, + {file = "PyGLM-2.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62a3615d97fc253785f246b84c23771fc1303a59a62e732bf70f144d189f63e5"}, + {file = "PyGLM-2.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:32071c21dce494aa02a37ea4236495fb2ad9110a104c14c6b45d9b5c366886c6"}, + {file = "PyGLM-2.7.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ead25748e90f98f925e8c2ea5ebc52f2cea184cbb12fa95bfa35b699a4f6f75"}, + {file = "PyGLM-2.7.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b59d847790a7a6ade9af59d4a42c75b411fbfb1994a2c51a699f94a2b2b70ee"}, + {file = "PyGLM-2.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5a8a9b74846118023f635f460a3d7498740996605eb25a875fe46c01e7ad23d"}, + {file = "PyGLM-2.7.1-cp310-cp310-win32.whl", hash = "sha256:82f96329235da787f0c3b30239a3b6da6c7c140ac1488b3142b363d870171fd0"}, + {file = "PyGLM-2.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:bf3e062103a95a78930201df100156e6ac24eaa9bd50ed59442300ba8edf3173"}, + {file = "PyGLM-2.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:4b07ab88d8108085e7998b17f72e246dc60ee195aa0967980a22a433e1c5693a"}, + {file = "PyGLM-2.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64b6ceca09f7881eaa9299121b343e6569d4ef300f483903801ce2f3407859c3"}, + {file = "PyGLM-2.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6833db36d6b2dc12e2a5a4dbc37f8e66c743235431af5b820f87ce35c7994052"}, + {file = "PyGLM-2.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad243f7e4c0a89e1a8943446592653a8dc899cbbeeeb3e33221fb146eeda0d"}, + {file = "PyGLM-2.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61f2eb79e17b2470e538070d3bdd81bac01399fb9065674e905636f7a0e3df57"}, + {file = "PyGLM-2.7.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aee0a971df967677f6b761c1fd9515a2e35a4e85ba2ab147dd7bf887fb2ab026"}, + {file = "PyGLM-2.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd6a1a620eb6431034fee42ad9c6cb46633be47f271f993bd135b24953dbfd6"}, + {file = "PyGLM-2.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e258d812b68be1ff7699b55d6e66430cdd35af8c06e0774b5673bc6ed913eac"}, + {file = "PyGLM-2.7.1-cp311-cp311-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5f804e9394befc35f2a2a2b2d3372288206538b9c75325c128441acd0a487104"}, + {file = "PyGLM-2.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4b34195081a6216aa6c85d56cbc6411e2d4cd6433664904bdb069b9bee18e1e"}, + {file = "PyGLM-2.7.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:60487a1b2db6307f92648e52cd5773fb3cf3d3f6112b56787e480e1109e18be9"}, + {file = "PyGLM-2.7.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:567b1664942d12fb45bbd54fcd2ab128882905de859da8cd8a866f1489ae7d48"}, + {file = "PyGLM-2.7.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b10e46a573ce412f16fa14cca34dfca96c23e2ccf159e3202809d6e77f140758"}, + {file = "PyGLM-2.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45427bc7fe0d164c4f5e71bcc8920e6ee8169c0d1526bc6c6274b2c21fbfe8ec"}, + {file = "PyGLM-2.7.1-cp311-cp311-win32.whl", hash = "sha256:583caf00a8afa07850ede7fc82547f72b532f6dfce9aed25648f655dae415f51"}, + {file = "PyGLM-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:fd3e7c92ba74e76f992c6cfcbd662058c076329695713108ae54af233e6647d8"}, + {file = "PyGLM-2.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:e22b4a83e0fb36f68a1d84cfd2a95d66d053d0cc5a8aab08bcd63eb7cca11d25"}, + {file = "PyGLM-2.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4aa958556dd26e647c67c740aee2822bd41f05f6f2ce70c4f1268e7f0a93b108"}, + {file = "PyGLM-2.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6bf06d63629a2f0f2a85c1fac778b334ee6864d1768ac469b31a30cafa596c24"}, + {file = "PyGLM-2.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750109e5a2d788395766ea6465b96762b42d779c9cb430dd75c02c447eb0379d"}, + {file = "PyGLM-2.7.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3486bc2183c78d5fba7f8e4a26c5093bb4df5110cd09f45e7416f3afab688f72"}, + {file = "PyGLM-2.7.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f4f7d138826d95aaf32bb1414978acc6d839a09350a33c244b881c6e84bd9d5"}, + {file = "PyGLM-2.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9c3c55a8070b2894b1b22bc96ed01832613f4ea984b5023b56a6b4cd6c1d1"}, + {file = "PyGLM-2.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fcc6ef1f3041618300313af8cd0ba4bb60758d083d7759b51ff6c709e935677"}, + {file = "PyGLM-2.7.1-cp312-cp312-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06b2d950e221178cc0832b5b78d91f13926d37b851bc4737f92bd5149482b487"}, + {file = "PyGLM-2.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:117ea85a40144720436f296c8f1eacb03c33d90fac3df5af086813d7a9e04881"}, + {file = "PyGLM-2.7.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4c4190604c5af3f65981c4d51aff2681baaa1c185d9d380daeb0ba391199ddcb"}, + {file = "PyGLM-2.7.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0f42987c0eedfe6a17131e59b72133f5465eda445fc6c3e4c90c7bafe2258914"}, + {file = "PyGLM-2.7.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fc97b41cbb89e4135ec2abc2a1ed2fb6ee2c72eadd2e9f1dca2d9dcef90a47dc"}, + {file = "PyGLM-2.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7e448d37621641fa12469ad6c19d05d7d70bd258cea334a18e2123063f6fd1ed"}, + {file = "PyGLM-2.7.1-cp312-cp312-win32.whl", hash = "sha256:f5488a46d1de944daaf043c209c149ddc59a4f1cced32ef5670062f1e0f3c813"}, + {file = "PyGLM-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:890db36101cf945cc1470685ea8fed788291088cdfa425fcfdf701ee6adfd24f"}, + {file = "PyGLM-2.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:9e9adc73094c2607841209c08f1dc61808630f2113f7cd58003a4904fe241e56"}, + {file = "PyGLM-2.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5116c118f7f291ab6b19b7980f8c32be9fee9d88cf85ef1760651df3f14a2cb4"}, + {file = "PyGLM-2.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0e2bc62c3308da7c3fb80da569d8482c17929e7e4f161c0520db7db8e1238162"}, + {file = "PyGLM-2.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa46162c9a427f7625880daf85d0bfc426792b3a5e465219967644391cfdbf82"}, + {file = "PyGLM-2.7.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85b713430b30a1eac179b90f59dbccb82dccba1659eab7ff5d6db6d5a07c8b7c"}, + {file = "PyGLM-2.7.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34555f20024e5ab86ed8ec7236a8ffddd12521d3093d16fc5da1fc032bc7af7"}, + {file = "PyGLM-2.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e391b2e7f5af43e5838bfbd1eb1d4ce13187248abc6fe462bef3f23044ef8845"}, + {file = "PyGLM-2.7.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d87dbdc96a34e0a6f06dfccfdb012ab70249c049606cb01b1c51cd5262a0ff26"}, + {file = "PyGLM-2.7.1-cp38-cp38-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38d66b7150fd0bfe37f2d31dc67969b6c4145b3f2921ec115be3047f3350b015"}, + {file = "PyGLM-2.7.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ab4b5325cbd9515ab6fd0c1d1d1e9c4cfc62fe7ae5c816cc82e41c2ed5ad65"}, + {file = "PyGLM-2.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:40b7a96e13c782be23140f05bc4e74599fa89922df533445a69e7a392365550a"}, + {file = "PyGLM-2.7.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b1b3b469eeb4f988db25deae6b31ddf89cf67f7623e4a74793a4d6041832163"}, + {file = "PyGLM-2.7.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ffd4c70ae50ec33592cc537aeb991230d5ff8dd00091dc880fb566be69776514"}, + {file = "PyGLM-2.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:202964258049588469eb355bd1baeb0bffe5aa5d6fb34f4983ff465ec03a6156"}, + {file = "PyGLM-2.7.1-cp38-cp38-win32.whl", hash = "sha256:e6ef596e75df4dd233511a403725885443763d700a9b425408be62e1de980a75"}, + {file = "PyGLM-2.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f8950d1d6d900bde0b966248ee604219a7760766bd13ab3892f80384d53633c"}, + {file = "PyGLM-2.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d3ba1bbce59dee76afceff1342bc29c0bfced2a55baf024505187d5d162a2253"}, + {file = "PyGLM-2.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19218d8f414c86dbf4c6b97bf8c040e2e4fd5885833fc3a5ac5e3a2a15d9afdc"}, + {file = "PyGLM-2.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf719550ac93107a6c9f75bb08f9baa5d7e846fb88222985e18c4611365d48ba"}, + {file = "PyGLM-2.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33398e48bedd6e8f8945c7b7b0c82495927d0c5ed1964f2ca963b30b668d9275"}, + {file = "PyGLM-2.7.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d9f45eb2f1e8be53a8f52317014ebd12f61a8feffbefb413ab828323f3c0bd6"}, + {file = "PyGLM-2.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba24cd208fedb4d1cc3a49f52c84eebc30aaae67c9f094cee75765566451f19"}, + {file = "PyGLM-2.7.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2c36b9fffd80a25765e6917e3aecda7ab264bbfd5a8fad531c57d0c0403fda6"}, + {file = "PyGLM-2.7.1-cp39-cp39-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dbe2abda1572796ce981eb468d91c9031813448c9e2e426e8567617ca77c3a6"}, + {file = "PyGLM-2.7.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7253575ec5f269ee5f5e2960e5f0d5148e3e978a27ad7ecdb0b3fce22fb6f3a"}, + {file = "PyGLM-2.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6581986fa3908f3f7e8df8145f7e4dc52a285f0a589caf43cd40003304fe4048"}, + {file = "PyGLM-2.7.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5c70210bdd103314cb635647343afc48db66e1207cbb4e389f0fe75e5ca4ae82"}, + {file = "PyGLM-2.7.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6f75fe54190bba4d8b86a8fe0c1f4d71183c401fdc0c398129b25bc4c67a6d1"}, + {file = "PyGLM-2.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:298ffdf5f7c638ff31ae74e547710485258de7fc2a819932b2df7cc8de5f8265"}, + {file = "PyGLM-2.7.1-cp39-cp39-win32.whl", hash = "sha256:303c301139c92853cd7b280431463af7c8e0d638f7d24d248c2aa19fd1316f06"}, + {file = "PyGLM-2.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:d5b0ba4cf238f916b24e0bb6411469d1348f66165d7121d34231799eb74820b9"}, + {file = "PyGLM-2.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:449bdb7cf05f675631254886ccc58691167ac869bae29303fb9f869fe8bb3c46"}, + {file = "PyGLM-2.7.1.tar.gz", hash = "sha256:455817299e431c9a95e75b0c6ef6007caf57ee12a89fbba660f3741dd4da8f88"}, ] [[package]] name = "pyright" -version = "1.1.374" +version = "1.1.375" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.374-py3-none-any.whl", hash = "sha256:55752bcf7a3646d293cd76710a983b71e16f6128aab2d42468e6eb7e46c0a70d"}, - {file = "pyright-1.1.374.tar.gz", hash = "sha256:d01b2daf864ba5e0362e56b844984865970d7204158e61eb685e2dab7804cb82"}, + {file = "pyright-1.1.375-py3-none-any.whl", hash = "sha256:4c5e27eddeaee8b41cc3120736a1dda6ae120edf8523bb2446b6073a52f286e3"}, + {file = "pyright-1.1.375.tar.gz", hash = "sha256:7765557b0d6782b2fadabff455da2014476404c9e9214f49977a4e49dec19a0f"}, ] [package.dependencies] @@ -121,32 +285,32 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "ruff" -version = "0.5.6" +version = "0.5.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"}, - {file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"}, - {file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"}, - {file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"}, - {file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"}, - {file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"}, - {file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"}, + {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, + {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, + {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"}, + {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"}, + {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"}, + {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"}, + {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, ] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8af4bef3c7c98ce6f8824ba54749e122641a4d117e61aca5f23c1860c8d9579a" +content-hash = "9a61dd776fe3d1db4fab8ffce4b1a533f543b0461c2449c6c6a015be6990a45e" diff --git a/episode-13/pyproject.toml b/episode-13/pyproject.toml index cadaea1..ce16fa9 100644 --- a/episode-13/pyproject.toml +++ b/episode-13/pyproject.toml @@ -14,15 +14,24 @@ authors = [ ] readme = "README.md" +[build-system] +requires = ["poetry-core", "setuptools", "cython"] + +[tool.poetry.build] +script = "build.py" +generate-setup-file = true + [tool.poetry.dependencies] python = "^3.10" pyglet = "^2.0.16" nbtlib = "^2.0.4" base36 = "^0.1.1" +pyglm = "^2.7.1" [tool.poetry.group.dev.dependencies] ruff = "^0.5.5" pyright = "^1.1.374" +cython = "^3.0.11" [tool.pyright] exclude = [".venv"] @@ -34,6 +43,6 @@ venv = ".venv" reportIndexIssue = false # From https://github.com/obiwac/python-minecraft-clone/pull/107: -# F405 * may be undefined, or defined from star imports: These are indeed defined from star imports. I guess we could import all the symbols in '__all__' explicitly, but if there's a mistake here and it causes a runtime error, that's not the end of the world. +# F405 X may be undefined, or defined from star imports: These are indeed defined from star imports. I guess we could import all the symbols in '__all__' explicitly, but if there's a mistake here and it causes a runtime error, that's not the end of the world. ignore = ["models/__init__.py"] diff --git a/episode-13/src/chunk/__init__.py b/episode-13/src/chunk/__init__.py index e69de29..94b3dd9 100644 --- a/episode-13/src/chunk/__init__.py +++ b/episode-13/src/chunk/__init__.py @@ -0,0 +1,11 @@ +from src.chunk.common import CHUNK_HEIGHT, CHUNK_LENGTH, CHUNK_WIDTH +from src.chunk.common import SUBCHUNK_HEIGHT, SUBCHUNK_LENGTH, SUBCHUNK_WIDTH + +__all__ = [ + "CHUNK_WIDTH", + "CHUNK_HEIGHT", + "CHUNK_LENGTH", + "SUBCHUNK_WIDTH", + "SUBCHUNK_HEIGHT", + "SUBCHUNK_LENGTH", +] diff --git a/episode-13/src/chunk/__init__.pyx b/episode-13/src/chunk/__init__.pyx new file mode 100644 index 0000000..40c1d5d --- /dev/null +++ b/episode-13/src/chunk/__init__.pyx @@ -0,0 +1,60 @@ +from src.chunk.common import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_LENGTH +from src.chunk.common import SUBCHUNK_WIDTH, SUBCHUNK_HEIGHT, SUBCHUNK_LENGTH + +from libc.stdlib cimport malloc, free +from libc.string cimport memset +from libc.stdint cimport uint8_t, uint32_t + +cdef int C_CHUNK_WIDTH = CHUNK_WIDTH +cdef int C_CHUNK_HEIGHT = CHUNK_HEIGHT +cdef int C_CHUNK_LENGTH = CHUNK_LENGTH + +cdef int C_SUBCHUNK_WIDTH = SUBCHUNK_WIDTH +cdef int C_SUBCHUNK_HEIGHT = SUBCHUNK_HEIGHT +cdef int C_SUBCHUNK_LENGTH = SUBCHUNK_LENGTH + +cdef class CSubchunk: + cdef size_t data_count + cdef float* data + + cdef size_t index_count + cdef uint32_t* indices + + def __init__(self): + self.data_count = 0 + self.index_count = 0 + +cdef class CChunk: + cdef size_t data_count + cdef float* data + + cdef size_t index_count + cdef int* indices + + cdef size_t size + cdef uint8_t* blocks + + def __init__(self): + self.size = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_LENGTH * sizeof(self.blocks[0]) + self.blocks = malloc(self.size) + memset(self.blocks, 0, self.size) + + def __del__(self): + free(self.blocks) + + @property + def index_count(self): + return self.index_count + + def get_blocks(self, i): + return self.blocks[i] + + def set_blocks(self, i, val): + self.blocks[i] = val + + def copy_blocks(self, blocks): + cdef int i + cdef int length = len(blocks) + + for i in range(length): + self.blocks[i] = blocks[i] diff --git a/episode-13/src/chunk/chunk.py b/episode-13/src/chunk/chunk.py deleted file mode 100644 index b2049a2..0000000 --- a/episode-13/src/chunk/chunk.py +++ /dev/null @@ -1,193 +0,0 @@ -import ctypes -import math - -import pyglet.gl as gl - -from src.chunk.subchunk import ( - SUBCHUNK_HEIGHT, - SUBCHUNK_LENGTH, - SUBCHUNK_WIDTH, - Subchunk, -) - -CHUNK_WIDTH = 16 -CHUNK_HEIGHT = 128 -CHUNK_LENGTH = 16 - - -class Chunk: - def __init__(self, world, chunk_position): - self.world = world - - self.modified = False - self.chunk_position = chunk_position - - self.position = ( - self.chunk_position[0] * CHUNK_WIDTH, - self.chunk_position[1] * CHUNK_HEIGHT, - self.chunk_position[2] * CHUNK_LENGTH, - ) - - self.blocks = [[[0 for _ in range(CHUNK_LENGTH)] for _ in range(CHUNK_HEIGHT)] for _ in range(CHUNK_WIDTH)] - - self.subchunks = {} - - for x in range(int(CHUNK_WIDTH / SUBCHUNK_WIDTH)): - for y in range(int(CHUNK_HEIGHT / SUBCHUNK_HEIGHT)): - for z in range(int(CHUNK_LENGTH / SUBCHUNK_LENGTH)): - self.subchunks[(x, y, z)] = Subchunk(self, (x, y, z)) - - # mesh variables - - self.mesh_vertex_positions = [] - self.mesh_tex_coords = [] - self.mesh_shading_values = [] - - self.mesh_index_counter = 0 - self.mesh_indices = [] - - # create VAO and VBO's - - self.vao = gl.GLuint(0) - gl.glGenVertexArrays(1, self.vao) - gl.glBindVertexArray(self.vao) - - self.vertex_position_vbo = gl.GLuint(0) - gl.glGenBuffers(1, self.vertex_position_vbo) - - self.tex_coord_vbo = gl.GLuint(0) - gl.glGenBuffers(1, self.tex_coord_vbo) - - self.shading_values_vbo = gl.GLuint(0) - gl.glGenBuffers(1, self.shading_values_vbo) - - self.ibo = gl.GLuint(0) - gl.glGenBuffers(1, self.ibo) - - def update_subchunk_meshes(self): - for subchunk_position in self.subchunks: - subchunk = self.subchunks[subchunk_position] - subchunk.update_mesh() - - def update_at_position(self, position): - x, y, z = position - - lx = int(x % SUBCHUNK_WIDTH) - ly = int(y % SUBCHUNK_HEIGHT) - lz = int(z % SUBCHUNK_LENGTH) - - clx, cly, clz = self.world.get_local_position(position) - - sx = math.floor(clx / SUBCHUNK_WIDTH) - sy = math.floor(cly / SUBCHUNK_HEIGHT) - sz = math.floor(clz / SUBCHUNK_LENGTH) - - self.subchunks[(sx, sy, sz)].update_mesh() - - def try_update_subchunk_mesh(subchunk_position): - if subchunk_position in self.subchunks: - self.subchunks[subchunk_position].update_mesh() - - if lx == SUBCHUNK_WIDTH - 1: - try_update_subchunk_mesh((sx + 1, sy, sz)) - if lx == 0: - try_update_subchunk_mesh((sx - 1, sy, sz)) - - if ly == SUBCHUNK_HEIGHT - 1: - try_update_subchunk_mesh((sx, sy + 1, sz)) - if ly == 0: - try_update_subchunk_mesh((sx, sy - 1, sz)) - - if lz == SUBCHUNK_LENGTH - 1: - try_update_subchunk_mesh((sx, sy, sz + 1)) - if lz == 0: - try_update_subchunk_mesh((sx, sy, sz - 1)) - - def update_mesh(self): - # combine all the small subchunk meshes into one big chunk mesh - - self.mesh_vertex_positions = [] - self.mesh_tex_coords = [] - self.mesh_shading_values = [] - - self.mesh_index_counter = 0 - self.mesh_indices = [] - - for subchunk_position in self.subchunks: - subchunk = self.subchunks[subchunk_position] - - self.mesh_vertex_positions.extend(subchunk.mesh_vertex_positions) - self.mesh_tex_coords.extend(subchunk.mesh_tex_coords) - self.mesh_shading_values.extend(subchunk.mesh_shading_values) - - mesh_indices = [index + self.mesh_index_counter for index in subchunk.mesh_indices] - - self.mesh_indices.extend(mesh_indices) - self.mesh_index_counter += subchunk.mesh_index_counter - - # send the full mesh data to the GPU and free the memory used client-side (we don't need it anymore) - # don't forget to save the length of 'self.mesh_indices' before freeing - - self.mesh_indices_length = len(self.mesh_indices) - self.send_mesh_data_to_gpu() - - del self.mesh_vertex_positions - del self.mesh_tex_coords - del self.mesh_shading_values - - del self.mesh_indices - - def send_mesh_data_to_gpu(self): # pass mesh data to gpu - if not self.mesh_index_counter: - return - - gl.glBindVertexArray(self.vao) - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertex_position_vbo) - gl.glBufferData( - gl.GL_ARRAY_BUFFER, - ctypes.sizeof(gl.GLfloat * len(self.mesh_vertex_positions)), - (gl.GLfloat * len(self.mesh_vertex_positions))(*self.mesh_vertex_positions), - gl.GL_STATIC_DRAW, - ) - - gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, 0) - gl.glEnableVertexAttribArray(0) - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.tex_coord_vbo) - gl.glBufferData( - gl.GL_ARRAY_BUFFER, - ctypes.sizeof(gl.GLfloat * len(self.mesh_tex_coords)), - (gl.GLfloat * len(self.mesh_tex_coords))(*self.mesh_tex_coords), - gl.GL_STATIC_DRAW, - ) - - gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, 0) - gl.glEnableVertexAttribArray(1) - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.shading_values_vbo) - gl.glBufferData( - gl.GL_ARRAY_BUFFER, - ctypes.sizeof(gl.GLfloat * len(self.mesh_shading_values)), - (gl.GLfloat * len(self.mesh_shading_values))(*self.mesh_shading_values), - gl.GL_STATIC_DRAW, - ) - - gl.glVertexAttribPointer(2, 1, gl.GL_FLOAT, gl.GL_FALSE, 0, 0) - gl.glEnableVertexAttribArray(2) - - gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.ibo) - gl.glBufferData( - gl.GL_ELEMENT_ARRAY_BUFFER, - ctypes.sizeof(gl.GLuint * self.mesh_indices_length), - (gl.GLuint * self.mesh_indices_length)(*self.mesh_indices), - gl.GL_STATIC_DRAW, - ) - - def draw(self): - if not self.mesh_index_counter: - return - - gl.glBindVertexArray(self.vao) - - gl.glDrawElements(gl.GL_TRIANGLES, self.mesh_indices_length, gl.GL_UNSIGNED_INT, None) diff --git a/episode-13/src/chunk/chunk.pyx b/episode-13/src/chunk/chunk.pyx new file mode 100644 index 0000000..c07b280 --- /dev/null +++ b/episode-13/src/chunk/chunk.pyx @@ -0,0 +1,191 @@ +import ctypes +import array +import math + +import pyglet.gl as gl + +from libc.stdlib cimport malloc, free +from libc.stdint cimport uint8_t +from src.chunk cimport CChunk, CSubchunk +from src.chunk import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_LENGTH +from src.chunk import SUBCHUNK_WIDTH, SUBCHUNK_HEIGHT, SUBCHUNK_LENGTH + +# define these first because subchunk depends on them + +import src.chunk.subchunk as subchunk + +cdef send_mesh_data_to_gpu(self): # pass mesh data to gpu + cdef CChunk c = self.c + + if not c.index_count: + return + + gl.glBindVertexArray(self.vao) + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vbo) + gl.glBufferData( + gl.GL_ARRAY_BUFFER, + c.data_count * sizeof(c.data[0]), + ctypes.cast(c.data, ctypes.POINTER(gl.GLfloat)), + gl.GL_STATIC_DRAW) + + f = ctypes.sizeof(gl.GLfloat) + + gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 7 * f, 0 * f) + gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, 7 * f, 3 * f) + gl.glVertexAttribPointer(2, 1, gl.GL_FLOAT, gl.GL_FALSE, 7 * f, 6 * f) + + gl.glEnableVertexAttribArray(0) + gl.glEnableVertexAttribArray(1) + gl.glEnableVertexAttribArray(2) + + gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.ibo) + gl.glBufferData( + gl.GL_ELEMENT_ARRAY_BUFFER, + c.index_count * sizeof(c.indices[0]), + ctypes.cast(c.indices, ctypes.POINTER(gl.GLuint)), + gl.GL_STATIC_DRAW) + +cdef update_mesh(self): + cdef CChunk c = self.c + + # combine all the small subchunk meshes into one big chunk mesh + + cdef int target_data_count = 0 + cdef int target_index_count = 0 + + cdef CSubchunk subchunk_c + + for subchunk in self.subchunks.values(): + subchunk_c = subchunk.c + + target_data_count += subchunk_c.data_count + target_index_count += subchunk_c.index_count + + c.data_count = 0 + c.data = malloc(target_data_count * sizeof(c.data[0])) + + c.index_count = 0 + c.indices = malloc(target_index_count * sizeof(c.indices[0])) + + cdef size_t i + + for subchunk_position in self.subchunks: + subchunk = self.subchunks[subchunk_position] + subchunk_c = subchunk.c + + c.data[c.data_count: c.data_count + subchunk_c.data_count] = subchunk_c.data + c.data_count += subchunk_c.data_count + + for i in range(subchunk_c.index_count): + c.indices[c.index_count + i] = subchunk_c.indices[i] + c.index_count // 6 * 4 + + c.index_count += subchunk_c.index_count + + # send the full mesh data to the GPU and free the memory used client-side (we don't need it anymore) + # don't forget to save the length of 'self.mesh_indices' before freeing + + send_mesh_data_to_gpu(self) + + free(c.data) + free(c.indices) + +class Chunk: + def __init__(self, world, chunk_position): + self.world = world + + self.modified = False + self.chunk_position = chunk_position + + self.position = ( + self.chunk_position[0] * CHUNK_WIDTH, + self.chunk_position[1] * CHUNK_HEIGHT, + self.chunk_position[2] * CHUNK_LENGTH) + + self.subchunks = {} + + for x in range(int(CHUNK_WIDTH / SUBCHUNK_WIDTH)): + for y in range(int(CHUNK_HEIGHT / SUBCHUNK_HEIGHT)): + for z in range(int(CHUNK_LENGTH / SUBCHUNK_LENGTH)): + self.subchunks[(x, y, z)] = subchunk.Subchunk(self, (x, y, z)) + + self.c = CChunk() + + # create VAO, VBO, and IBO + + self.vao = gl.GLuint(0) + gl.glGenVertexArrays(1, self.vao) + gl.glBindVertexArray(self.vao) + + self.vbo = gl.GLuint(0) + gl.glGenBuffers(1, self.vbo) + + self.ibo = gl.GLuint(0) + gl.glGenBuffers(1, self.ibo) + + @property + def loaded(self): + return self.c.index_count > 0 + + def get_block(self, x, y, z): + return self.c.get_blocks( + x * CHUNK_LENGTH * CHUNK_HEIGHT + + z * CHUNK_HEIGHT + + y) + + def set_block(self, x, y, z, block): + self.c.set_blocks( + x * CHUNK_LENGTH * CHUNK_HEIGHT + + z * CHUNK_HEIGHT + + y, block) + + def copy_blocks(self, blocks): + self.c.copy_blocks(blocks) + + def update_subchunk_meshes(self): + for subchunk_position in self.subchunks: + subchunk = self.subchunks[subchunk_position] + subchunk.update_mesh() + + def update_at_position(self, position): + x, y, z = position + + lx = int(x % SUBCHUNK_WIDTH ) + ly = int(y % SUBCHUNK_HEIGHT) + lz = int(z % SUBCHUNK_LENGTH) + + clx, cly, clz = self.world.get_local_position(position) + + sx = math.floor(clx / SUBCHUNK_WIDTH) + sy = math.floor(cly / SUBCHUNK_HEIGHT) + sz = math.floor(clz / SUBCHUNK_LENGTH) + + self.subchunks[(sx, sy, sz)].update_mesh() + + def try_update_subchunk_mesh(subchunk_position): + if subchunk_position in self.subchunks: + self.subchunks[subchunk_position].update_mesh() + + if lx == SUBCHUNK_WIDTH - 1: try_update_subchunk_mesh((sx + 1, sy, sz)) + if lx == 0: try_update_subchunk_mesh((sx - 1, sy, sz)) + + if ly == SUBCHUNK_HEIGHT - 1: try_update_subchunk_mesh((sx, sy + 1, sz)) + if ly == 0: try_update_subchunk_mesh((sx, sy - 1, sz)) + + if lz == SUBCHUNK_LENGTH - 1: try_update_subchunk_mesh((sx, sy, sz + 1)) + if lz == 0: try_update_subchunk_mesh((sx, sy, sz - 1)) + + def update_mesh(self): + update_mesh(self) + + def draw(self): + if not self.loaded: + return + + gl.glBindVertexArray(self.vao) + + gl.glDrawElements( + gl.GL_TRIANGLES, + self.c.index_count, + gl.GL_UNSIGNED_INT, + None) diff --git a/episode-13/src/chunk/common.py b/episode-13/src/chunk/common.py new file mode 100644 index 0000000..e5c2f2c --- /dev/null +++ b/episode-13/src/chunk/common.py @@ -0,0 +1,7 @@ +CHUNK_WIDTH = 16 +CHUNK_HEIGHT = 128 +CHUNK_LENGTH = 16 + +SUBCHUNK_WIDTH = 4 +SUBCHUNK_HEIGHT = 4 +SUBCHUNK_LENGTH = 4 diff --git a/episode-13/src/chunk/subchunk.py b/episode-13/src/chunk/subchunk.py deleted file mode 100644 index 1683a96..0000000 --- a/episode-13/src/chunk/subchunk.py +++ /dev/null @@ -1,109 +0,0 @@ -SUBCHUNK_WIDTH = 4 -SUBCHUNK_HEIGHT = 4 -SUBCHUNK_LENGTH = 4 - - -class Subchunk: - def __init__(self, parent, subchunk_position): - self.parent = parent - self.world = self.parent.world - - self.subchunk_position = subchunk_position - - self.local_position = ( - self.subchunk_position[0] * SUBCHUNK_WIDTH, - self.subchunk_position[1] * SUBCHUNK_HEIGHT, - self.subchunk_position[2] * SUBCHUNK_LENGTH, - ) - - self.position = ( - self.parent.position[0] + self.local_position[0], - self.parent.position[1] + self.local_position[1], - self.parent.position[2] + self.local_position[2], - ) - - # mesh variables - - self.mesh_vertex_positions = [] - self.mesh_tex_coords = [] - self.mesh_shading_values = [] - - self.mesh_index_counter = 0 - self.mesh_indices = [] - - def update_mesh(self): - self.mesh_vertex_positions = [] - self.mesh_tex_coords = [] - self.mesh_shading_values = [] - - self.mesh_index_counter = 0 - self.mesh_indices = [] - - def add_face(face): - vertex_positions = block_type.vertex_positions[face].copy() - - for i in range(4): - vertex_positions[i * 3 + 0] += x - vertex_positions[i * 3 + 1] += y - vertex_positions[i * 3 + 2] += z - - self.mesh_vertex_positions.extend(vertex_positions) - - indices = [0, 1, 2, 0, 2, 3] - for i in range(6): - indices[i] += self.mesh_index_counter - - self.mesh_indices.extend(indices) - self.mesh_index_counter += 4 - - self.mesh_tex_coords.extend(block_type.tex_coords[face]) - self.mesh_shading_values.extend(block_type.shading_values[face]) - - for local_x in range(SUBCHUNK_WIDTH): - for local_y in range(SUBCHUNK_HEIGHT): - for local_z in range(SUBCHUNK_LENGTH): - parent_lx = self.local_position[0] + local_x - parent_ly = self.local_position[1] + local_y - parent_lz = self.local_position[2] + local_z - - block_number = self.parent.blocks[parent_lx][parent_ly][parent_lz] - - if block_number: - block_type = self.world.block_types[block_number] - - x, y, z = ( - self.position[0] + local_x, - self.position[1] + local_y, - self.position[2] + local_z, - ) - - def can_render_face(position): - if not self.world.is_opaque_block(position): - if block_type.glass and self.world.get_block_number(position) == block_number: - return False - - return True - - return False - - # if block is cube, we want it to check neighbouring blocks so that we don't uselessly render faces - # if block isn't a cube, we just want to render all faces, regardless of neighbouring blocks - # since the vast majority of blocks are probably anyway going to be cubes, this won't impact performance all that much; the amount of useless faces drawn is going to be minimal - - if block_type.is_cube: - if can_render_face((x + 1, y, z)): - add_face(0) - if can_render_face((x - 1, y, z)): - add_face(1) - if can_render_face((x, y + 1, z)): - add_face(2) - if can_render_face((x, y - 1, z)): - add_face(3) - if can_render_face((x, y, z + 1)): - add_face(4) - if can_render_face((x, y, z - 1)): - add_face(5) - - else: - for i in range(len(block_type.vertex_positions)): - add_face(i) diff --git a/episode-13/src/chunk/subchunk.pyx b/episode-13/src/chunk/subchunk.pyx new file mode 100644 index 0000000..4f9b342 --- /dev/null +++ b/episode-13/src/chunk/subchunk.pyx @@ -0,0 +1,175 @@ +import src.chunk.chunk as chunk +from src.chunk cimport CChunk, CSubchunk +from src.chunk cimport C_CHUNK_WIDTH, C_CHUNK_HEIGHT, C_CHUNK_LENGTH +from src.chunk cimport C_SUBCHUNK_WIDTH, C_SUBCHUNK_HEIGHT, C_SUBCHUNK_LENGTH + +from libc.stdlib cimport malloc, realloc, free +from libc.string cimport memcpy +from libc.stdint cimport uint32_t, uint8_t +from pyglet.gl import gl + +cdef bint can_render_face(uint8_t* parent_blocks, chunks, block_types, int block_number, block_type, int x_, int y_, int z_): + cdef int x = x_ % C_CHUNK_WIDTH + cdef int y = y_ % C_CHUNK_HEIGHT + cdef int z = z_ % C_CHUNK_LENGTH + + cdef int adj_number = 0 + + cdef CChunk c_chunk + + # getting block number from adjacent chunks is relatively slow, but we can just index the chunk directly if we know we're not on the edges of it + + if x == 0 or x == C_CHUNK_WIDTH - 1 or y == 0 or y == C_CHUNK_HEIGHT - 1 or z == 0 or z == C_CHUNK_LENGTH - 1: + chunk_position = ( + x_ // C_CHUNK_WIDTH, + y_ // C_CHUNK_HEIGHT, + z_ // C_CHUNK_LENGTH) + + if chunk_position in chunks: + c_chunk = chunks[chunk_position].c + + adj_number = c_chunk.blocks[ + x * C_CHUNK_LENGTH * C_CHUNK_HEIGHT + + z * C_CHUNK_HEIGHT + + y] + + else: + adj_number = parent_blocks[ + x * C_CHUNK_LENGTH * C_CHUNK_HEIGHT + + z * C_CHUNK_HEIGHT + + y] + + adj_type = block_types[adj_number] + + if not adj_number or adj_type.transparent: # TODO getting transparent attribute of adjacent block incurs a lot of overhead + if block_type.glass and adj_number == block_number: # rich compare between adj_number and block_number prevented + return False + + return True + + return False + +cdef update_mesh(self): + cdef CSubchunk c = self.c + + if c.data_count: + free(c.data) + + if c.index_count: + free(c.indices) + + c.data_count = 0 + c.data = malloc(1) + + c.index_count = 0 + c.indices = malloc(1) + + def add_face(face): + vertex_positions = block_type.vertex_positions[face] + tex_coords = block_type.tex_coords[face] + shading_values = block_type.shading_values[face] + + cdef float[4 * 7] data + cdef size_t i + + for i in range(4): + data[i * 7 + 0] = vertex_positions[i * 3 + 0] + x + data[i * 7 + 1] = vertex_positions[i * 3 + 1] + y + data[i * 7 + 2] = vertex_positions[i * 3 + 2] + z + + data[i * 7 + 3] = tex_coords[i * 3 + 0] + data[i * 7 + 4] = tex_coords[i * 3 + 1] + data[i * 7 + 5] = tex_coords[i * 3 + 2] + + data[i * 7 + 6] = shading_values[i] + + # TODO make realloc not increment one at a time + + c.data_count += sizeof(data) // sizeof(data[0]) + c.data = realloc(c.data, c.data_count * sizeof(data[0])) + memcpy(c.data + c.data_count * sizeof(data[0]) - sizeof(data), data, sizeof(data)) + + cdef uint32_t[6] indices = [0, 1, 2, 0, 2, 3] + + for i in range(6): + indices[i] += c.index_count // 6 * 4 + + c.index_count += sizeof(indices) // sizeof(indices[0]) + c.indices = realloc(c.indices, c.index_count * sizeof(indices[0])) + memcpy(c.indices + c.index_count * sizeof(indices[0]) - sizeof(indices), indices, sizeof(indices)) + + chunks = self.world.chunks + block_types = self.world.block_types + + cdef CChunk c_parent = self.parent.c + cdef uint8_t* parent_blocks = c_parent.blocks + + cdef int slx = self.local_position[0] + cdef int sly = self.local_position[1] + cdef int slz = self.local_position[2] + + cdef int sx = self.position[0] + cdef int sy = self.position[1] + cdef int sz = self.position[2] + + cdef int x, y, z + cdef int parent_lx, parent_ly, parent_lz + cdef int block_number + cdef int local_x, local_y, local_z + + for local_x in range(C_SUBCHUNK_WIDTH): + for local_y in range(C_SUBCHUNK_HEIGHT): + for local_z in range(C_SUBCHUNK_LENGTH): + parent_lx = slx + local_x + parent_ly = sly + local_y + parent_lz = slz + local_z + + block_number = parent_blocks[ + parent_lx * C_CHUNK_LENGTH * C_CHUNK_HEIGHT + + parent_lz * C_CHUNK_HEIGHT + + parent_ly] + + if block_number: + block_type = block_types[block_number] + + x = sx + local_x + y = sy + local_y + z = sz + local_z + + # if block is cube, we want it to check neighbouring blocks so that we don't uselessly render faces + # if block isn't a cube, we just want to render all faces, regardless of neighbouring blocks + # since the vast majority of blocks are probably anyway going to be cubes, this won't impact performance all that much; the amount of useless faces drawn is going to be minimal + + if block_type.is_cube: + if can_render_face(parent_blocks, chunks, block_types, block_number, block_type, x + 1, y, z): add_face(0) + if can_render_face(parent_blocks, chunks, block_types, block_number, block_type, x - 1, y, z): add_face(1) + if can_render_face(parent_blocks, chunks, block_types, block_number, block_type, x, y + 1, z): add_face(2) + if can_render_face(parent_blocks, chunks, block_types, block_number, block_type, x, y - 1, z): add_face(3) + if can_render_face(parent_blocks, chunks, block_types, block_number, block_type, x, y, z + 1): add_face(4) + if can_render_face(parent_blocks, chunks, block_types, block_number, block_type, x, y, z - 1): add_face(5) + + else: + for i in range(len(block_type.vertex_positions)): + add_face(i) + +class Subchunk: + def __init__(self, parent, subchunk_position): + self.parent = parent + self.world = self.parent.world + + self.subchunk_position = subchunk_position + + self.local_position = ( + self.subchunk_position[0] * C_SUBCHUNK_WIDTH, + self.subchunk_position[1] * C_SUBCHUNK_HEIGHT, + self.subchunk_position[2] * C_SUBCHUNK_LENGTH) + + self.position = ( + self.parent.position[0] + self.local_position[0], + self.parent.position[1] + self.local_position[1], + self.parent.position[2] + self.local_position[2]) + + self.c = CSubchunk() + + def update_mesh(self): + update_mesh(self) diff --git a/episode-13/src/save.py b/episode-13/src/save.py index 9fbc40a..25d98e1 100644 --- a/episode-13/src/save.py +++ b/episode-13/src/save.py @@ -1,3 +1,6 @@ +import gzip +import os +import pickle import nbtlib as nbt import base36 @@ -23,35 +26,51 @@ def chunk_position_to_path(self, chunk_position): return chunk_path + def chunk_position_to_nbt_and_cache_path(self, chunk_position): + chunk_path = self.chunk_position_to_path(chunk_position) + cache_path = f"{chunk_path}.cache" + + return chunk_path, cache_path + def load_chunk(self, chunk_position): - # load the chunk file + # Load the chunk file. - chunk_path = self.chunk_position_to_path(chunk_position) + chunk_path, cache_path = self.chunk_position_to_nbt_and_cache_path(chunk_position) try: - chunk_blocks = nbt.load(chunk_path)["Level"]["Blocks"] + # Invalidate cache if the chunk file is newer than it. + + if os.path.getmtime(cache_path) < os.path.getmtime(chunk_path): + raise FileNotFoundError() + + with gzip.open(cache_path) as f: + blocks = pickle.load(f) except FileNotFoundError: - return + try: + chunk_data = nbt.load(chunk_path) + blocks = list(map(int, chunk_data["Level"]["Blocks"])) - # create chunk and fill it with the blocks from our chunk file + # Cache blocks. - self.world.chunks[chunk_position] = Chunk(self.world, chunk_position) + with gzip.open(cache_path, "wb") as f: + pickle.dump(blocks, f) - for x in range(CHUNK_WIDTH): - for y in range(CHUNK_HEIGHT): - for z in range(CHUNK_LENGTH): - self.world.chunks[chunk_position].blocks[x][y][z] = chunk_blocks[ - x * CHUNK_LENGTH * CHUNK_HEIGHT + z * CHUNK_HEIGHT + y - ] + except FileNotFoundError: + return # Fail quietly if chunk file not found. + + # Create chunk and fill it with the blocks from our chunk file. + + self.world.chunks[chunk_position] = Chunk(self.world, chunk_position) + self.world.chunks[chunk_position].copy_blocks(blocks) def save_chunk(self, chunk_position): x, y, z = chunk_position - # try to load the chunk file - # if it doesn't exist, create a new one + # Try to load the chunk file. + # If it doesn't exist, create a new one. - chunk_path = self.chunk_position_to_path(chunk_position) + chunk_path, cache_path = self.chunk_position_to_nbt_and_cache_path(chunk_position) try: chunk_data = nbt.load(chunk_path) @@ -62,7 +81,7 @@ def save_chunk(self, chunk_position): chunk_data["Level"]["xPos"] = x chunk_data["Level"]["zPos"] = z - # fill the chunk file with the blocks from our chunk + # Fill the chunk file with the blocks from our chunk. chunk_blocks = nbt.ByteArray([0] * (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_LENGTH)) @@ -72,7 +91,12 @@ def save_chunk(self, chunk_position): block = self.world.chunks[chunk_position].blocks[x][y][z] chunk_blocks[x * CHUNK_LENGTH * CHUNK_HEIGHT + z * CHUNK_HEIGHT + y] = block - # save the chunk file + # Save a cache file. + + with gzip.open(cache_path, "wb") as f: + pickle.dump(chunk_blocks, f) + + # Save the chunk file. chunk_data["Level"]["Blocks"] = chunk_blocks chunk_data.save(chunk_path, gzipped=True) @@ -82,12 +106,8 @@ def load(self): # for y in range(-15, 16): # self.load_chunk((x, 0, y)) - # for x in range(-4, 4): - # for y in range(-4, 4): - # self.load_chunk((x, 0, y)) - - for x in range(-1, 1): - for y in range(-1, 1): + for x in range(-4, 4): + for y in range(-4, 4): self.load_chunk((x, 0, y)) def save(self): diff --git a/episode-13/src/world.py b/episode-13/src/world.py index 7520242..83fa9ea 100644 --- a/episode-13/src/world.py +++ b/episode-13/src/world.py @@ -1,5 +1,6 @@ import math -from src.chunk.chunk import CHUNK_HEIGHT, CHUNK_LENGTH, CHUNK_WIDTH, Chunk +from src.chunk import CHUNK_HEIGHT, CHUNK_LENGTH, CHUNK_WIDTH +from src.chunk.chunk import Chunk from src.save import Save from src.renderer.block_type import BlockType from src.renderer.texture_manager import TextureManager @@ -76,10 +77,6 @@ def __init__(self): self.chunks = {} self.save.load() - for chunk_position in self.chunks: - self.chunks[chunk_position].update_subchunk_meshes() - self.chunks[chunk_position].update_mesh() - def get_chunk_position(self, position): x, y, z = position @@ -100,9 +97,9 @@ def get_block_number(self, position): if chunk_position not in self.chunks: return 0 - lx, ly, lz = self.get_local_position(position) + pos = self.get_local_position(position) - block_number = self.chunks[chunk_position].blocks[lx][ly][lz] + block_number = self.chunks[chunk_position].get_block(*pos) return block_number def is_opaque_block(self, position):