cuju-render 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cuju/render/__init__.py +759 -0
- cuju/render/__main__.py +1065 -0
- cuju/render/_export/__init__.py +3 -0
- cuju/render/_export/feed.py +542 -0
- cuju/render/_export/sizes.py +157 -0
- cuju/render/_export/video.py +1618 -0
- cuju/render/_export/video_jobs.py +408 -0
- cuju/render/_format/__init__.py +3 -0
- cuju/render/_format/annotations.py +235 -0
- cuju/render/_format/codec.py +307 -0
- cuju/render/_format/keep.py +121 -0
- cuju/render/_format/recording.py +860 -0
- cuju/render/_format/writer.py +54 -0
- cuju/render/_guide/__init__.py +3 -0
- cuju/render/_guide/doctor.py +523 -0
- cuju/render/_guide/help.py +62 -0
- cuju/render/_guide/tutorial.py +254 -0
- cuju/render/_http/__init__.py +3 -0
- cuju/render/_http/commands.py +57 -0
- cuju/render/_http/headers.py +103 -0
- cuju/render/_http/launch.py +500 -0
- cuju/render/_http/launchers.py +408 -0
- cuju/render/_http/pwa.py +128 -0
- cuju/render/_http/server.py +2105 -0
- cuju/render/_http/window.py +360 -0
- cuju/render/_moments/__init__.py +3 -0
- cuju/render/_moments/captions.py +284 -0
- cuju/render/_moments/events.py +1123 -0
- cuju/render/_moments/search.py +694 -0
- cuju/render/_packages/__init__.py +3 -0
- cuju/render/_packages/glsl.py +746 -0
- cuju/render/_packages/loudness.py +334 -0
- cuju/render/_packages/pack.py +1959 -0
- cuju/render/_packages/presentation.py +1376 -0
- cuju/render/_packages/vocabulary.py +609 -0
- cuju/render/_playing/__init__.py +3 -0
- cuju/render/_playing/achievements.py +463 -0
- cuju/render/_playing/bench.py +609 -0
- cuju/render/_playing/challenges.py +1813 -0
- cuju/render/_playing/demo.py +323 -0
- cuju/render/_playing/draw.py +211 -0
- cuju/render/_playing/errors.py +20 -0
- cuju/render/_playing/game.py +645 -0
- cuju/render/_playing/limits.py +339 -0
- cuju/render/_playing/play.py +926 -0
- cuju/render/_playing/policy_worker.py +250 -0
- cuju/render/_playing/strict.py +810 -0
- cuju/render/_registry/__init__.py +3 -0
- cuju/render/_registry/clips.py +394 -0
- cuju/render/_registry/joints.py +108 -0
- cuju/render/_registry/teams.py +2058 -0
- cuju/render/_store/__init__.py +3 -0
- cuju/render/_store/home.py +70 -0
- cuju/render/_store/library.py +604 -0
- cuju/render/_store/samples.py +163 -0
- cuju/render/_text/__init__.py +3 -0
- cuju/render/_text/languages.py +637 -0
- cuju/render/_text/messages.py +1396 -0
- cuju/render/_tournaments/__init__.py +3 -0
- cuju/render/_tournaments/results.py +547 -0
- cuju/render/_tournaments/tournament.py +2742 -0
- cuju/render/_tournaments/tournament_cli.py +587 -0
- cuju/render/_tournaments/tournament_runner.py +1975 -0
- cuju/render/_version.py +6 -0
- cuju/render/_watch/__init__.py +3 -0
- cuju/render/_watch/grid.py +265 -0
- cuju/render/_watch/live.py +664 -0
- cuju/render/_watch/notebook.py +79 -0
- cuju/render/_watch/show.py +171 -0
- cuju/render/samples/sample-11v11.cjr +0 -0
- cuju/render/samples/sample-5v5.cjr +0 -0
- cuju/render/samples/teams/starter/main.py +33 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_easy.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_hard.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_medium.py +46 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_easy.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_hard.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_medium.py +34 -0
- cuju/render/web/assets/adboard_polygon.webp +0 -0
- cuju/render/web/assets/adboard_your_ad_here.webp +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-combatmelee.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-general.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementadvanced.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementbasic.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-simulation.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-special.glb +0 -0
- cuju/render/web/assets/ball.glb +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/corner.mp4 +0 -0
- cuju/render/web/assets/challenges/corner.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.webp +0 -0
- cuju/render/web/assets/challenges/manifest.json +375 -0
- cuju/render/web/assets/challenges/one_on_one.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one.webp +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.webp +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.mp4 +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.webp +0 -0
- cuju/render/web/assets/crowd.webp +0 -0
- cuju/render/web/assets/crowd_normal.webp +0 -0
- cuju/render/web/assets/cuju-logo-384.png +0 -0
- cuju/render/web/assets/cuju-logo.png +0 -0
- cuju/render/web/assets/cuju-logo.svg +46 -0
- cuju/render/web/assets/cuju-mark-light.svg +33 -0
- cuju/render/web/assets/cuju-mark.svg +33 -0
- cuju/render/web/assets/goal.glb +0 -0
- cuju/render/web/assets/grass.webp +0 -0
- cuju/render/web/assets/hair_black.png +0 -0
- cuju/render/web/assets/hair_blonde.png +0 -0
- cuju/render/web/assets/hair_brown.png +0 -0
- cuju/render/web/assets/hair_darkblonde.png +0 -0
- cuju/render/web/assets/hair_long01.glb +0 -0
- cuju/render/web/assets/hair_long02.glb +0 -0
- cuju/render/web/assets/hair_medium01.glb +0 -0
- cuju/render/web/assets/hair_red.png +0 -0
- cuju/render/web/assets/hair_short01.glb +0 -0
- cuju/render/web/assets/hair_short02.glb +0 -0
- cuju/render/web/assets/icons/cuju-192.png +0 -0
- cuju/render/web/assets/icons/cuju-512.png +0 -0
- cuju/render/web/assets/icons/cuju-maskable-512.png +0 -0
- cuju/render/web/assets/kit_goalkeeper.webp +0 -0
- cuju/render/web/assets/kit_mask.png +0 -0
- cuju/render/web/assets/kit_normal.webp +0 -0
- cuju/render/web/assets/manifest.json +601 -0
- cuju/render/web/assets/models/kaykit-barbarian.glb +0 -0
- cuju/render/web/assets/models/kaykit-barbarian.json +23 -0
- cuju/render/web/assets/models/kaykit-knight.glb +0 -0
- cuju/render/web/assets/models/kaykit-knight.json +23 -0
- cuju/render/web/assets/models/kaykit-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.glb +0 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.json +23 -0
- cuju/render/web/assets/models/kaykit-ranger.glb +0 -0
- cuju/render/web/assets/models/kaykit-ranger.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.json +23 -0
- cuju/render/web/assets/pitch.webp +0 -0
- cuju/render/web/assets/pitch_normal.webp +0 -0
- cuju/render/web/assets/player.glb +0 -0
- cuju/render/web/assets/shoe.webp +0 -0
- cuju/render/web/assets/shoe_sole.png +0 -0
- cuju/render/web/assets/skin_01.png +0 -0
- cuju/render/web/assets/skin_02.png +0 -0
- cuju/render/web/assets/skin_03.png +0 -0
- cuju/render/web/assets/skin_04.png +0 -0
- cuju/render/web/assets/sounds/applause.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-1.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-2.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-3.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-4.ogg +0 -0
- cuju/render/web/assets/sounds/city-horn.ogg +0 -0
- cuju/render/web/assets/sounds/city-loop.ogg +0 -0
- cuju/render/web/assets/sounds/city-siren.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-cheer.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-loop.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-ooh.ogg +0 -0
- cuju/render/web/assets/sounds/gulls.ogg +0 -0
- cuju/render/web/assets/sounds/neon-sting.ogg +0 -0
- cuju/render/web/assets/sounds/rain-loop.ogg +0 -0
- cuju/render/web/assets/sounds/sounds.json +46 -0
- cuju/render/web/assets/sounds/space-beep.ogg +0 -0
- cuju/render/web/assets/sounds/space-hum.ogg +0 -0
- cuju/render/web/assets/sounds/space-radio.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-long.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-short.ogg +0 -0
- cuju/render/web/assets/sounds/wind-loop.ogg +0 -0
- cuju/render/web/easy.css +357 -0
- cuju/render/web/export.html +23 -0
- cuju/render/web/feed.css +262 -0
- cuju/render/web/feed.html +28 -0
- cuju/render/web/flat.css +120 -0
- cuju/render/web/home.css +837 -0
- cuju/render/web/index.html +373 -0
- cuju/render/web/js/boot.js +50 -0
- cuju/render/web/js/broadcast/audio.js +2141 -0
- cuju/render/web/js/broadcast/celebrate.js +1238 -0
- cuju/render/web/js/broadcast/decisions.js +609 -0
- cuju/render/web/js/broadcast/hud.js +352 -0
- cuju/render/web/js/broadcast/show.js +3614 -0
- cuju/render/web/js/broadcast/transition.js +2231 -0
- cuju/render/web/js/broadcast/voices.js +153 -0
- cuju/render/web/js/capture.js +101 -0
- cuju/render/web/js/core/accessibility.js +90 -0
- cuju/render/web/js/core/api.js +429 -0
- cuju/render/web/js/core/comfort.js +135 -0
- cuju/render/web/js/core/i18n.js +705 -0
- cuju/render/web/js/core/icons.js +167 -0
- cuju/render/web/js/core/input-settings.js +156 -0
- cuju/render/web/js/core/keys.js +277 -0
- cuju/render/web/js/core/language-ui.js +125 -0
- cuju/render/web/js/core/menu.js +853 -0
- cuju/render/web/js/core/names.js +113 -0
- cuju/render/web/js/core/page-structure.js +110 -0
- cuju/render/web/js/core/scenarios.js +109 -0
- cuju/render/web/js/core/spoilers.js +236 -0
- cuju/render/web/js/core/typeahead.js +38 -0
- cuju/render/web/js/export/dom-picture.js +330 -0
- cuju/render/web/js/export/export-host.js +97 -0
- cuju/render/web/js/export/feed-dialog.js +191 -0
- cuju/render/web/js/export/feed-params.js +266 -0
- cuju/render/web/js/export/feed.js +363 -0
- cuju/render/web/js/export/mp4-mux.js +295 -0
- cuju/render/web/js/export/photo-core.js +313 -0
- cuju/render/web/js/export/photo.js +1314 -0
- cuju/render/web/js/export/safe-frame.js +59 -0
- cuju/render/web/js/export/size-fields.js +103 -0
- cuju/render/web/js/export/sizes.js +178 -0
- cuju/render/web/js/export/video-boot.js +55 -0
- cuju/render/web/js/export/video-browser.js +325 -0
- cuju/render/web/js/export/video-export.js +417 -0
- cuju/render/web/js/export/video-plan.js +207 -0
- cuju/render/web/js/export/video-range.js +185 -0
- cuju/render/web/js/export/webm-mux.js +198 -0
- cuju/render/web/js/feed-boot.js +151 -0
- cuju/render/web/js/feed-frame.js +101 -0
- cuju/render/web/js/help/doctor.js +488 -0
- cuju/render/web/js/help/firstrun.js +379 -0
- cuju/render/web/js/help/help-center.js +438 -0
- cuju/render/web/js/help/install-offer.js +164 -0
- cuju/render/web/js/help/install-state.js +104 -0
- cuju/render/web/js/help/lessons-core.js +207 -0
- cuju/render/web/js/help/lessons.js +463 -0
- cuju/render/web/js/help/palette.js +584 -0
- cuju/render/web/js/help/pwa.js +257 -0
- cuju/render/web/js/help/tour-core.js +241 -0
- cuju/render/web/js/help/tour.js +461 -0
- cuju/render/web/js/main.js +2513 -0
- cuju/render/web/js/pages/draw.js +188 -0
- cuju/render/web/js/pages/fieldpage.js +530 -0
- cuju/render/web/js/pages/home.js +521 -0
- cuju/render/web/js/pages/matches.js +779 -0
- cuju/render/web/js/pages/moments-ui.js +359 -0
- cuju/render/web/js/pages/queue-filters.js +63 -0
- cuju/render/web/js/pages/recording-groups.js +80 -0
- cuju/render/web/js/pages/recordings.js +1069 -0
- cuju/render/web/js/play/bench.js +347 -0
- cuju/render/web/js/play/challenges.js +1302 -0
- cuju/render/web/js/play/medals.js +127 -0
- cuju/render/web/js/play/play.js +1953 -0
- cuju/render/web/js/play/teasers.js +196 -0
- cuju/render/web/js/play/touch.js +350 -0
- cuju/render/web/js/play/tournament-layout.js +487 -0
- cuju/render/web/js/play/tournament-results.js +441 -0
- cuju/render/web/js/play/tournament-strict.js +560 -0
- cuju/render/web/js/play/tournament.js +2585 -0
- cuju/render/web/js/players/animate.js +972 -0
- cuju/render/web/js/players/clips.js +224 -0
- cuju/render/web/js/players/kaykit.js +162 -0
- cuju/render/web/js/players/kits.js +676 -0
- cuju/render/web/js/players/number-patch.js +82 -0
- cuju/render/web/js/players/players.js +2694 -0
- cuju/render/web/js/players/recolor.js +893 -0
- cuju/render/web/js/players/retarget.js +325 -0
- cuju/render/web/js/players/rig-numbers.js +174 -0
- cuju/render/web/js/presentation/animator.js +231 -0
- cuju/render/web/js/presentation/assign.js +85 -0
- cuju/render/web/js/presentation/behaviors.js +440 -0
- cuju/render/web/js/presentation/budget.js +209 -0
- cuju/render/web/js/presentation/catalog.js +1674 -0
- cuju/render/web/js/presentation/clocks.js +72 -0
- cuju/render/web/js/presentation/commands.js +301 -0
- cuju/render/web/js/presentation/contract.js +240 -0
- cuju/render/web/js/presentation/effects.js +904 -0
- cuju/render/web/js/presentation/flash.js +605 -0
- cuju/render/web/js/presentation/freeze-worklet.js +22 -0
- cuju/render/web/js/presentation/glsl.js +464 -0
- cuju/render/web/js/presentation/host.js +323 -0
- cuju/render/web/js/presentation/index.js +2066 -0
- cuju/render/web/js/presentation/loudness-worklet.js +235 -0
- cuju/render/web/js/presentation/loudness.js +740 -0
- cuju/render/web/js/presentation/moments.js +536 -0
- cuju/render/web/js/presentation/post.js +327 -0
- cuju/render/web/js/presentation/preview.js +180 -0
- cuju/render/web/js/presentation/protocol.js +174 -0
- cuju/render/web/js/presentation/reach.js +158 -0
- cuju/render/web/js/presentation/rigging.js +130 -0
- cuju/render/web/js/presentation/sandbox.js +157 -0
- cuju/render/web/js/presentation/shaders.js +235 -0
- cuju/render/web/js/presentation/stage.js +402 -0
- cuju/render/web/js/presentation/teamaudio.js +722 -0
- cuju/render/web/js/presentation/tracks.js +197 -0
- cuju/render/web/js/presentation/values.js +81 -0
- cuju/render/web/js/recording/annotation-data.js +203 -0
- cuju/render/web/js/recording/captions.js +491 -0
- cuju/render/web/js/recording/codec.js +185 -0
- cuju/render/web/js/recording/decode-worker.js +70 -0
- cuju/render/web/js/recording/decode.js +94 -0
- cuju/render/web/js/recording/episodes.js +46 -0
- cuju/render/web/js/recording/format.js +461 -0
- cuju/render/web/js/recording/live.js +348 -0
- cuju/render/web/js/recording/moments-query.js +59 -0
- cuju/render/web/js/recording/moments.js +149 -0
- cuju/render/web/js/recording/pose.js +86 -0
- cuju/render/web/js/recording/timeline.js +275 -0
- cuju/render/web/js/teams/art.js +50 -0
- cuju/render/web/js/teams/clip-editor.js +293 -0
- cuju/render/web/js/teams/editor.js +1979 -0
- cuju/render/web/js/teams/library.js +344 -0
- cuju/render/web/js/teams/painter.js +633 -0
- cuju/render/web/js/teams/surprise-ui.js +195 -0
- cuju/render/web/js/teams/surprise.js +497 -0
- cuju/render/web/js/teams/team-check.js +127 -0
- cuju/render/web/js/watch/annotations.js +270 -0
- cuju/render/web/js/watch/flat.js +366 -0
- cuju/render/web/js/watch/grid.js +436 -0
- cuju/render/web/js/watch/pitch2d.js +289 -0
- cuju/render/web/js/watch/player-tools.js +192 -0
- cuju/render/web/js/watch/presets.js +201 -0
- cuju/render/web/js/world/arenas.js +997 -0
- cuju/render/web/js/world/ball.js +204 -0
- cuju/render/web/js/world/cameras.js +982 -0
- cuju/render/web/js/world/crowd.js +376 -0
- cuju/render/web/js/world/field.js +227 -0
- cuju/render/web/js/world/framing.js +145 -0
- cuju/render/web/js/world/occlusion.js +48 -0
- cuju/render/web/js/world/pitch.js +703 -0
- cuju/render/web/js/world/scene.js +395 -0
- cuju/render/web/js/world/sky.js +325 -0
- cuju/render/web/js/world/stadium.js +1262 -0
- cuju/render/web/js/world/vendor-sky.js +367 -0
- cuju/render/web/js/world/weather.js +264 -0
- cuju/render/web/languages/en.json +4750 -0
- cuju/render/web/lessons.json +137 -0
- cuju/render/web/manifest.webmanifest +23 -0
- cuju/render/web/style.css +8399 -0
- cuju/render/web/sw.js +235 -0
- cuju/render/web/vendor/fabric/LICENSE +16 -0
- cuju/render/web/vendor/fabric/fabric.min.mjs +1 -0
- cuju/render/web/vendor/lucide/LICENSE +43 -0
- cuju/render/web/vendor/lucide/icons.js +141 -0
- cuju/render/web/vendor/three/LICENSE +21 -0
- cuju/render/web/vendor/three/build/three.core.js +60586 -0
- cuju/render/web/vendor/three/build/three.module.js +19719 -0
- cuju/render/web/vendor/three/examples/jsm/loaders/GLTFLoader.js +4925 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/EffectComposer.js +365 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/MaskPass.js +195 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/OutputPass.js +148 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/Pass.js +191 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/RenderPass.js +193 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/ShaderPass.js +135 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/UnrealBloomPass.js +541 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/CopyShader.js +52 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/LuminosityHighPassShader.js +68 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/OutputShader.js +103 -0
- cuju/render/web/vendor/three/examples/jsm/utils/BufferGeometryUtils.js +1501 -0
- cuju/render/web/vendor/three/examples/jsm/utils/SkeletonUtils.js +496 -0
- cuju_render-0.1.0.dist-info/METADATA +116 -0
- cuju_render-0.1.0.dist-info/RECORD +382 -0
- cuju_render-0.1.0.dist-info/WHEEL +4 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSE +202 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +396 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC0-1.0.txt +127 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/ISC.txt +16 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/MIT.txt +24 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/Unlicense.txt +13 -0
- cuju_render-0.1.0.dist-info/licenses/NOTICE +35 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Copyright 2026 The cuju Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Holds a team's process under strict rules to its limits of memory, threads and processes.
|
|
5
|
+
|
|
6
|
+
Each system enforces what it can, and the guard watches the rest four times a second.
|
|
7
|
+
|
|
8
|
+
- On Windows the process runs in a job object, which limits the memory that it commits, allows it
|
|
9
|
+
no second process and ends it with the job, so the system itself refuses what goes beyond. The
|
|
10
|
+
launcher of a virtual environment, which starts the interpreter as a process of its own, counts
|
|
11
|
+
as the first process of the job and stays out of the count of threads. The guard counts the
|
|
12
|
+
threads, and reads the job's peak of memory to name a forfeit.
|
|
13
|
+
- On Linux the process starts with a limit of its data segment, `RLIMIT_DATA`, which counts the
|
|
14
|
+
memory it allocates and leaves alone the address space that PyTorch reserves without using it,
|
|
15
|
+
which `RLIMIT_AS` would refuse. The guard reads its resident memory, its threads and its
|
|
16
|
+
children from `/proc`.
|
|
17
|
+
- On macOS, whose limits of data the system does not hold allocations to, the guard reads the
|
|
18
|
+
resident memory, the threads and the children of the process with `ps`.
|
|
19
|
+
|
|
20
|
+
The process itself also refuses to start another process once its team has loaded, by an audit
|
|
21
|
+
hook of Python, so the forfeit names the reason on every system, while the job object of Windows
|
|
22
|
+
stands behind it. While a team loads, Linux and macOS allow a short helper process, as Python's
|
|
23
|
+
ctypes runs one on Linux to find a library.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import contextlib
|
|
29
|
+
import os
|
|
30
|
+
import subprocess
|
|
31
|
+
import sys
|
|
32
|
+
import threading
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
# The threads of a team's process beyond those the rules give it, for the interpreter's main
|
|
36
|
+
# thread, the thread that watches the match and those that the system and libraries keep, such
|
|
37
|
+
# as the few that PyTorch and the loader of Windows start whatever the limit of threads.
|
|
38
|
+
THREAD_ALLOWANCE = 10
|
|
39
|
+
# The seconds between two looks at a process.
|
|
40
|
+
_INTERVAL = 0.25
|
|
41
|
+
_MB = 1024 * 1024
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _launched() -> bool:
|
|
45
|
+
"""Returns whether Python starts through the launcher of a virtual environment on Windows.
|
|
46
|
+
|
|
47
|
+
That launcher runs the interpreter as a process of its own beside it, which the job of a
|
|
48
|
+
team's process then holds as well, and which the guard counts apart.
|
|
49
|
+
"""
|
|
50
|
+
base = getattr(sys, '_base_executable', sys.executable)
|
|
51
|
+
return os.name == 'nt' and os.path.normcase(base) != os.path.normcase(sys.executable)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Guard:
|
|
55
|
+
"""Watches one team's process, and ends it once it breaks a limit.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
memory_mb: The memory that the process may use, in megabytes.
|
|
59
|
+
threads: The threads that the rules give the process, before the allowance.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
broken: The limit that the process broke, `(kind, values)` with the kind `memory`,
|
|
63
|
+
`threads` or `process`, or `None`.
|
|
64
|
+
peak_mb: The most memory that the process was seen to use, in megabytes.
|
|
65
|
+
enforced: What the system itself holds the process to, as the names of the limits.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, memory_mb: float, threads: int) -> None:
|
|
69
|
+
self.memory = int(memory_mb * _MB)
|
|
70
|
+
self.memory_mb = memory_mb
|
|
71
|
+
self.threads = threads + THREAD_ALLOWANCE
|
|
72
|
+
self.broken: tuple[str, dict[str, Any]] | None = None
|
|
73
|
+
self.peak_mb = 0.0
|
|
74
|
+
self.enforced: list[str] = []
|
|
75
|
+
# Whether the process may have children, while its team loads, as libraries may run a
|
|
76
|
+
# helper process to find their files. The match turns it off once the team has loaded.
|
|
77
|
+
self.loading = True
|
|
78
|
+
self._process: Any = None
|
|
79
|
+
self._job: Any = None
|
|
80
|
+
self._stop = threading.Event()
|
|
81
|
+
|
|
82
|
+
def preexec(self) -> Any:
|
|
83
|
+
"""Returns what a process runs before Python starts on Linux and macOS, or `None`."""
|
|
84
|
+
if os.name == 'nt':
|
|
85
|
+
return None
|
|
86
|
+
limit = self.memory
|
|
87
|
+
|
|
88
|
+
def limited() -> None:
|
|
89
|
+
import resource
|
|
90
|
+
|
|
91
|
+
with contextlib.suppress(ValueError, OSError):
|
|
92
|
+
resource.setrlimit(resource.RLIMIT_DATA, (limit, limit))
|
|
93
|
+
|
|
94
|
+
if sys.platform.startswith('linux'):
|
|
95
|
+
self.enforced.append('memory')
|
|
96
|
+
return limited
|
|
97
|
+
|
|
98
|
+
def attach(self, process: Any) -> None:
|
|
99
|
+
"""Puts a started process under the guard, in a job object on Windows."""
|
|
100
|
+
self._process = process
|
|
101
|
+
if os.name == 'nt':
|
|
102
|
+
with contextlib.suppress(OSError, AttributeError):
|
|
103
|
+
self._job = _Job(self.memory, int(process._handle), 2 if _launched() else 1)
|
|
104
|
+
self.enforced += ['memory', 'process']
|
|
105
|
+
threading.Thread(target=self._watch, daemon=True, name='cuju-strict-guard').start()
|
|
106
|
+
|
|
107
|
+
def _break(self, kind: str, **values: Any) -> None:
|
|
108
|
+
if self.broken is None:
|
|
109
|
+
self.broken = (kind, values)
|
|
110
|
+
with contextlib.suppress(OSError):
|
|
111
|
+
self._process.kill()
|
|
112
|
+
|
|
113
|
+
def _watch(self) -> None:
|
|
114
|
+
pid = self._process.pid
|
|
115
|
+
while not self._stop.wait(_INTERVAL) and self._process.poll() is None:
|
|
116
|
+
try:
|
|
117
|
+
memory, threads, children = _look(pid, self._job)
|
|
118
|
+
except (OSError, ValueError, subprocess.SubprocessError):
|
|
119
|
+
continue
|
|
120
|
+
if memory is not None:
|
|
121
|
+
self.peak_mb = max(self.peak_mb, memory / _MB)
|
|
122
|
+
if memory > self.memory:
|
|
123
|
+
self._break('memory', used=f'{memory / _MB:.0f}', limit=f'{self.memory_mb:g}')
|
|
124
|
+
return
|
|
125
|
+
if threads is not None and threads > self.threads:
|
|
126
|
+
self._break('threads', found=str(threads), limit=str(self.threads))
|
|
127
|
+
return
|
|
128
|
+
if children and not self.loading:
|
|
129
|
+
self._break('process')
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
def peak(self) -> float:
|
|
133
|
+
"""Returns the most memory that the process used, from the job object where it has one."""
|
|
134
|
+
if self._job is not None:
|
|
135
|
+
with contextlib.suppress(OSError):
|
|
136
|
+
self.peak_mb = max(self.peak_mb, self._job.peak() / _MB)
|
|
137
|
+
return self.peak_mb
|
|
138
|
+
|
|
139
|
+
def close(self) -> None:
|
|
140
|
+
"""Stops watching and closes the job object, which ends what is left of the process."""
|
|
141
|
+
self._stop.set()
|
|
142
|
+
if self._job is not None:
|
|
143
|
+
self.peak()
|
|
144
|
+
self._job.close()
|
|
145
|
+
self._job = None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _look(pid: int, job: Any) -> tuple[int | None, int | None, int]:
|
|
149
|
+
"""Returns the resident memory in bytes, the threads and the children of a process."""
|
|
150
|
+
if os.name == 'nt':
|
|
151
|
+
if job is None:
|
|
152
|
+
return None, _windows_threads({pid}), 0
|
|
153
|
+
# The launcher of a virtual environment only waits for the interpreter, so its threads
|
|
154
|
+
# stay out of the count.
|
|
155
|
+
pids = set(job.pids()) - ({pid} if _launched() else set())
|
|
156
|
+
return job.peak(), _windows_threads(pids), job.extra_processes()
|
|
157
|
+
if sys.platform.startswith('linux'):
|
|
158
|
+
status = {}
|
|
159
|
+
with open(f'/proc/{pid}/status', encoding='ascii', errors='replace') as stream:
|
|
160
|
+
for line in stream:
|
|
161
|
+
name, _, value = line.partition(':')
|
|
162
|
+
status[name] = value.split()
|
|
163
|
+
memory = int(status['VmRSS'][0]) * 1024 if 'VmRSS' in status else None
|
|
164
|
+
threads = int(status['Threads'][0]) if 'Threads' in status else None
|
|
165
|
+
children = 0
|
|
166
|
+
for task in os.listdir(f'/proc/{pid}/task'):
|
|
167
|
+
with contextlib.suppress(OSError):
|
|
168
|
+
with open(f'/proc/{pid}/task/{task}/children', encoding='ascii') as stream:
|
|
169
|
+
children += len(stream.read().split())
|
|
170
|
+
return memory, threads, children
|
|
171
|
+
# macOS and other systems read the process with ps, which every one of them ships.
|
|
172
|
+
found = subprocess.run(
|
|
173
|
+
['ps', '-o', 'rss=', '-p', str(pid)],
|
|
174
|
+
capture_output=True,
|
|
175
|
+
text=True,
|
|
176
|
+
encoding='utf-8',
|
|
177
|
+
check=False,
|
|
178
|
+
).stdout.split()
|
|
179
|
+
memory = int(found[0]) * 1024 if found else None
|
|
180
|
+
lines = subprocess.run(
|
|
181
|
+
['ps', '-M', '-p', str(pid)], capture_output=True, text=True, encoding='utf-8', check=False
|
|
182
|
+
).stdout.splitlines()
|
|
183
|
+
threads = max(0, len(lines) - 1) or None
|
|
184
|
+
table = subprocess.run(
|
|
185
|
+
['ps', '-A', '-o', 'ppid='], capture_output=True, text=True, encoding='utf-8', check=False
|
|
186
|
+
).stdout.split()
|
|
187
|
+
return memory, threads, sum(1 for value in table if value == str(pid))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _windows_threads(pids: set[int]) -> int | None:
|
|
191
|
+
"""Returns the threads of processes on Windows, from a snapshot of the system's threads."""
|
|
192
|
+
import ctypes
|
|
193
|
+
from ctypes import wintypes
|
|
194
|
+
|
|
195
|
+
class Entry(ctypes.Structure):
|
|
196
|
+
_fields_ = (
|
|
197
|
+
('size', wintypes.DWORD),
|
|
198
|
+
('usage', wintypes.DWORD),
|
|
199
|
+
('thread', wintypes.DWORD),
|
|
200
|
+
('owner', wintypes.DWORD),
|
|
201
|
+
('base', wintypes.LONG),
|
|
202
|
+
('delta', wintypes.LONG),
|
|
203
|
+
('flags', wintypes.DWORD),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
kernel = ctypes.windll.kernel32
|
|
207
|
+
kernel.CreateToolhelp32Snapshot.restype = wintypes.HANDLE
|
|
208
|
+
snapshot = kernel.CreateToolhelp32Snapshot(4, 0)
|
|
209
|
+
if not snapshot or snapshot == wintypes.HANDLE(-1).value:
|
|
210
|
+
return None
|
|
211
|
+
try:
|
|
212
|
+
entry = Entry()
|
|
213
|
+
entry.size = ctypes.sizeof(Entry)
|
|
214
|
+
count = 0
|
|
215
|
+
found = kernel.Thread32First(snapshot, ctypes.byref(entry))
|
|
216
|
+
while found:
|
|
217
|
+
if entry.owner in pids:
|
|
218
|
+
count += 1
|
|
219
|
+
found = kernel.Thread32Next(snapshot, ctypes.byref(entry))
|
|
220
|
+
return count
|
|
221
|
+
finally:
|
|
222
|
+
kernel.CloseHandle(snapshot)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class _Job:
|
|
226
|
+
"""A job object of Windows that limits one process's memory and allows it no other process."""
|
|
227
|
+
|
|
228
|
+
# The limits of a job, the memory of each process, the processes at once and the end of
|
|
229
|
+
# every process with the job.
|
|
230
|
+
_PROCESS_MEMORY = 0x100
|
|
231
|
+
_ACTIVE_PROCESS = 0x8
|
|
232
|
+
_KILL_ON_CLOSE = 0x2000
|
|
233
|
+
|
|
234
|
+
def __init__(self, memory: int, handle: int, processes: int = 1) -> None:
|
|
235
|
+
import ctypes
|
|
236
|
+
from ctypes import wintypes
|
|
237
|
+
|
|
238
|
+
self._ctypes = ctypes
|
|
239
|
+
|
|
240
|
+
class Basic(ctypes.Structure):
|
|
241
|
+
_fields_ = (
|
|
242
|
+
('per_process_time', ctypes.c_int64),
|
|
243
|
+
('per_job_time', ctypes.c_int64),
|
|
244
|
+
('flags', wintypes.DWORD),
|
|
245
|
+
('minimum_working_set', ctypes.c_size_t),
|
|
246
|
+
('maximum_working_set', ctypes.c_size_t),
|
|
247
|
+
('active_processes', wintypes.DWORD),
|
|
248
|
+
('affinity', ctypes.c_size_t),
|
|
249
|
+
('priority', wintypes.DWORD),
|
|
250
|
+
('scheduling', wintypes.DWORD),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
class Counters(ctypes.Structure):
|
|
254
|
+
_fields_ = tuple((name, ctypes.c_uint64) for name in ('a', 'b', 'c', 'd', 'e', 'f'))
|
|
255
|
+
|
|
256
|
+
class Extended(ctypes.Structure):
|
|
257
|
+
_fields_ = (
|
|
258
|
+
('basic', Basic),
|
|
259
|
+
('io', Counters),
|
|
260
|
+
('process_memory', ctypes.c_size_t),
|
|
261
|
+
('job_memory', ctypes.c_size_t),
|
|
262
|
+
('peak_process_memory', ctypes.c_size_t),
|
|
263
|
+
('peak_job_memory', ctypes.c_size_t),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
class Accounting(ctypes.Structure):
|
|
267
|
+
_fields_ = (
|
|
268
|
+
('user', ctypes.c_int64),
|
|
269
|
+
('kernel', ctypes.c_int64),
|
|
270
|
+
('this_user', ctypes.c_int64),
|
|
271
|
+
('this_kernel', ctypes.c_int64),
|
|
272
|
+
('page_faults', wintypes.DWORD),
|
|
273
|
+
('total_processes', wintypes.DWORD),
|
|
274
|
+
('active_processes', wintypes.DWORD),
|
|
275
|
+
('terminated_processes', wintypes.DWORD),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
self._extended, self._accounting = Extended, Accounting
|
|
279
|
+
kernel = ctypes.windll.kernel32
|
|
280
|
+
kernel.CreateJobObjectW.restype = wintypes.HANDLE
|
|
281
|
+
self._kernel = kernel
|
|
282
|
+
self.handle = kernel.CreateJobObjectW(None, None)
|
|
283
|
+
if not self.handle:
|
|
284
|
+
raise OSError('cannot make a job object')
|
|
285
|
+
info = Extended()
|
|
286
|
+
info.basic.flags = self._PROCESS_MEMORY | self._ACTIVE_PROCESS | self._KILL_ON_CLOSE
|
|
287
|
+
info.basic.active_processes = processes
|
|
288
|
+
self.processes = processes
|
|
289
|
+
info.process_memory = memory
|
|
290
|
+
if not kernel.SetInformationJobObject(
|
|
291
|
+
wintypes.HANDLE(self.handle), 9, ctypes.byref(info), ctypes.sizeof(info)
|
|
292
|
+
):
|
|
293
|
+
self.close()
|
|
294
|
+
raise OSError('cannot limit a job object')
|
|
295
|
+
if not kernel.AssignProcessToJobObject(
|
|
296
|
+
wintypes.HANDLE(self.handle), wintypes.HANDLE(handle)
|
|
297
|
+
):
|
|
298
|
+
self.close()
|
|
299
|
+
raise OSError('cannot put the process in a job object')
|
|
300
|
+
|
|
301
|
+
def peak(self) -> int:
|
|
302
|
+
"""Returns the most memory that the process has committed, in bytes."""
|
|
303
|
+
ctypes = self._ctypes
|
|
304
|
+
info = self._extended()
|
|
305
|
+
self._kernel.QueryInformationJobObject(
|
|
306
|
+
ctypes.c_void_p(self.handle), 9, ctypes.byref(info), ctypes.sizeof(info), None
|
|
307
|
+
)
|
|
308
|
+
return int(info.peak_process_memory)
|
|
309
|
+
|
|
310
|
+
def extra_processes(self) -> int:
|
|
311
|
+
"""Returns the processes of the job beyond the team's own."""
|
|
312
|
+
ctypes = self._ctypes
|
|
313
|
+
info = self._accounting()
|
|
314
|
+
self._kernel.QueryInformationJobObject(
|
|
315
|
+
ctypes.c_void_p(self.handle), 1, ctypes.byref(info), ctypes.sizeof(info), None
|
|
316
|
+
)
|
|
317
|
+
return max(0, int(info.active_processes) - self.processes)
|
|
318
|
+
|
|
319
|
+
def pids(self) -> list[int]:
|
|
320
|
+
"""Returns the processes of the job."""
|
|
321
|
+
ctypes = self._ctypes
|
|
322
|
+
|
|
323
|
+
class Listed(ctypes.Structure):
|
|
324
|
+
_fields_ = (
|
|
325
|
+
('assigned', ctypes.c_uint32),
|
|
326
|
+
('listed', ctypes.c_uint32),
|
|
327
|
+
('ids', ctypes.c_size_t * 16),
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
info = Listed()
|
|
331
|
+
self._kernel.QueryInformationJobObject(
|
|
332
|
+
ctypes.c_void_p(self.handle), 3, ctypes.byref(info), ctypes.sizeof(info), None
|
|
333
|
+
)
|
|
334
|
+
return [int(info.ids[index]) for index in range(min(info.listed, 16))]
|
|
335
|
+
|
|
336
|
+
def close(self) -> None:
|
|
337
|
+
if self.handle:
|
|
338
|
+
self._kernel.CloseHandle(self._ctypes.c_void_p(self.handle))
|
|
339
|
+
self.handle = None
|