From 5639acb34149ab582ad661312a984fda9999f37e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:11:01 +0300 Subject: [PATCH 01/26] Run Unity 2D projects as Codename One applications A Unity project's Assets and ProjectSettings in common/src/main/unity build unmodified: the .NET SDK compiles the C# scripts, maven/cil-translator turns the assembly into Java class files and compiles every scene, prefab, animator controller, clip, tile map and particle system to reflection-free Java, and maven/unity-compat is the runtime they run against (a .NET base library, the UnityEngine API over com.codename1.gaming, and our own TextMesh Pro and Cinemachine implementations). Maven (compile-unity, import-unity-project) and Gradle (compileUnity) share UnityProjectBuilder. Both modules sit behind the unity-compat profile and are published by the release. Float results have to match C# on every target, so native builds get -ffp-contract=off and the JavaScript target rounds float results with Math.fround. Bugs this uncovered and fixes outside the new modules, each with a test: - ParparVM JavaScript: wide stack shuffles, MULTIANEWARRAY dimension pops, a call result compared with FCMP/DCMP evaluated more than once, SWAP losing the lower value. - ParparVM native: arraycopy/clone of an array of primitive arrays took the primitive path. - GameView: a key repeat released a held key; a focused hosted child kept the multi-touch arrays from the view. - Box2D: chain against chain threw in World.popContact; the broad phase allocated a merge buffer on every step above 31 pairs. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 66 +- .github/workflows/unity-compat.yml | 249 ++ .gitignore | 2 + CLAUDE.md | 201 +- .../src/com/codename1/gaming/GameView.java | 49 +- .../collision/broadphase/BroadPhase.java | 26 +- .../gaming/physics/box2d/dynamics/World.java | 8 + NOTICE | 43 + .../snippets/developer-guide/unity-interop.cs | 123 + .../snippets/developer-guide/unity-interop.sh | 40 + .../developer-guide/unity-interop.txt | 128 + docs/developer-guide/Unity-Interop.asciidoc | 1194 ++++++ docs/developer-guide/developer-guide.asciidoc | 2 + .../img/unity-interop-asteroids.png | Bin 0 -> 27898 bytes .../img/unity-interop-fruitopia-level.png | Bin 0 -> 104426 bytes .../img/unity-interop-fruitopia-menu.png | Bin 0 -> 110600 bytes .../img/unity-interop-fruitopia.png | Bin 0 -> 90653 bytes .../img/unity-interop-pong.png | Bin 0 -> 5400 bytes .../img/unity-interop-snake.png | Bin 0 -> 10129 bytes .../img/unity-interop-sokoban-complete.png | Bin 0 -> 9329 bytes .../img/unity-interop-sokoban.png | Bin 0 -> 9745 bytes docs/developer-guide/languagetool-accept.txt | 5 + maven/README.adoc | 8 + maven/build-engine/spotbugs-exclude.xml | 8 + .../com/codename1/maven/BackendPackager.java | 4 + .../com/codename1/maven/GradleConversion.java | 6 +- .../codename1/maven/UnityProjectBuilder.java | 943 +++++ .../codename1/maven/UnityProjectImporter.java | 241 ++ .../codename1/maven/GradleConversionTest.java | 25 + .../maven/UnityProjectBuilderTest.java | 471 +++ .../maven/UnityProjectImporterTest.java | 176 + maven/cil-translator/pom.xml | 206 + maven/cil-translator/spotbugs-exclude.xml | 64 + .../codename1/cil/metadata/CilAssembly.java | 749 ++++ .../cil/metadata/CilFormatException.java | 33 + .../com/codename1/cil/metadata/CilType.java | 334 ++ .../cil/metadata/MetadataReader.java | 467 +++ .../codename1/cil/metadata/MethodBody.java | 310 ++ .../codename1/cil/metadata/PortablePdb.java | 329 ++ .../cil/metadata/SignatureParser.java | 212 + .../com/codename1/cil/metadata/Universe.java | 161 + .../com/codename1/cil/tools/MetadataDump.java | 184 + .../java/com/codename1/cil/translate/Jvm.java | 248 ++ .../cil/translate/MethodTranslator.java | 2649 ++++++++++++ .../com/codename1/cil/translate/Names.java | 472 +++ .../codename1/cil/translate/RuntimeIndex.java | 227 + .../cil/translate/TranslationException.java | 34 + .../codename1/cil/translate/Translator.java | 1443 +++++++ .../codename1/cil/translate/TypeMapping.java | 55 + .../java/com/codename1/cil/translate/Val.java | 134 + .../unity/scenecompiler/AnimationAssets.java | 410 ++ .../unity/scenecompiler/ParticleAssets.java | 314 ++ .../unity/scenecompiler/SceneCompiler.java | 3640 +++++++++++++++++ .../unity/scenecompiler/TilemapAssets.java | 241 ++ .../unity/scenecompiler/UnityYaml.java | 402 ++ .../com/codename1/cil/UnityToolchain.java | 343 ++ .../cil/tools/MetadataReaderOracleTest.java | 67 + .../cil/translate/ConsoleSampleTest.java | 62 + .../cil/translate/DiagnosticsTest.java | 211 + .../unity/scenecompiler/AssetParsersTest.java | 431 ++ .../unity/scenecompiler/ResourcesTest.java | 242 ++ .../scenecompiler/Scene2dSampleTest.java | 140 + .../unity/scenecompiler/UnityYamlTest.java | 91 + .../archetype-resources/common/pom.xml | 28 + .../resources/archetype-resources/pom.xml | 5 + .../java/com/codename1/gradle/AppSupport.java | 92 + .../gradle/tasks/CompileUnityTask.java | 165 + .../gradle/tasks/TaskHelpersTest.java | 41 + .../com/codename1/maven/CompileUnityMojo.java | 172 + .../maven/ImportUnityProjectMojo.java | 125 + .../com/codename1/maven/UnityPomUpdater.java | 104 + .../codename1/maven/CompileUnityMojoTest.java | 113 + .../codename1/maven/UnityPomUpdaterTest.java | 112 + .../com/codename1/gaming/GameViewTest.java | 118 + .../broadphase/BroadPhasePairSortTest.java | 143 + .../dynamics/Box2dCollisionScenariosTest.java | 55 + .../unity-compat-project-test.sh | 303 ++ maven/integration-tests/unity-compat-test.sh | 419 ++ maven/pom.xml | 30 + maven/scripts/r2/README.md | 12 +- maven/unity-compat/pom.xml | 383 ++ maven/unity-compat/spotbugs-exclude.xml | 377 ++ maven/unity-compat/src/main/csharp/.gitignore | 3 + .../UnityEngine.Values.csproj | 31 + .../main/csharp/UnityEngine.Values/Values.cs | 1088 +++++ .../src/main/csharp/UnityEngine/Engine.cs | 1143 ++++++ .../csharp/UnityEngine/UnityEngine.csproj | 26 + .../unitycompat/app/UnityApplication.java | 246 ++ .../unitycompat/app/package-info.java | 25 + .../cinemachine/CinemachineBrain.java | 95 + .../cinemachine/CinemachineComponentBase.java | 50 + .../CinemachineFramingTransposer.java | 142 + .../cinemachine/CinemachineTransposer.java | 84 + .../cinemachine/CinemachineVirtualCamera.java | 108 + .../CinemachineVirtualCameraBase.java | 75 + .../unitycompat/cinemachine/LensSettings.java | 93 + .../codename1/unitycompat/system/Action.java | 49 + .../unitycompat/system/Action_1.java | 49 + .../unitycompat/system/Action_2.java | 49 + .../unitycompat/system/Action_3.java | 49 + .../unitycompat/system/ArgumentException.java | 40 + .../system/ArgumentNullException.java | 40 + .../system/ArgumentOutOfRangeException.java | 40 + .../system/ArithmeticException.java | 40 + .../unitycompat/system/ArrayView.java | 159 + .../codename1/unitycompat/system/Array_.java | 222 + .../unitycompat/system/Boolean_.java | 55 + .../codename1/unitycompat/system/Byte_.java | 51 + .../codename1/unitycompat/system/Char_.java | 69 + .../codename1/unitycompat/system/Console.java | 42 + .../unitycompat/system/Delegate.java | 127 + .../system/DivideByZeroException.java | 40 + .../codename1/unitycompat/system/Double_.java | 55 + .../codename1/unitycompat/system/EnumBox.java | 53 + .../codename1/unitycompat/system/Enum_.java | 162 + .../unitycompat/system/Environment.java | 40 + .../unitycompat/system/EventArgs.java | 28 + .../unitycompat/system/EventHandler.java | 47 + .../unitycompat/system/EventHandler_1.java | 47 + .../unitycompat/system/Exception.java | 63 + .../unitycompat/system/FormatException.java | 40 + .../codename1/unitycompat/system/Func_1.java | 51 + .../codename1/unitycompat/system/Func_2.java | 51 + .../codename1/unitycompat/system/Func_3.java | 51 + .../codename1/unitycompat/system/Func_4.java | 51 + .../unitycompat/system/IComparable.java | 29 + .../unitycompat/system/IComparable_1.java | 29 + .../unitycompat/system/IDisposable.java | 29 + .../unitycompat/system/IEquatable_1.java | 29 + .../system/IndexOutOfRangeException.java | 40 + .../codename1/unitycompat/system/Int16_.java | 55 + .../codename1/unitycompat/system/Int32_.java | 133 + .../codename1/unitycompat/system/Int64_.java | 55 + .../codename1/unitycompat/system/Interop.java | 438 ++ .../system/InvalidCastException.java | 40 + .../system/InvalidOperationException.java | 40 + .../codename1/unitycompat/system/Math.java | 277 ++ .../codename1/unitycompat/system/MdArray.java | 160 + .../unitycompat/system/MdArrayBoolean.java | 95 + .../unitycompat/system/MdArrayByte.java | 95 + .../unitycompat/system/MdArrayChar.java | 95 + .../unitycompat/system/MdArrayDouble.java | 95 + .../unitycompat/system/MdArrayFloat.java | 95 + .../unitycompat/system/MdArrayInt.java | 95 + .../unitycompat/system/MdArrayLong.java | 95 + .../unitycompat/system/MdArrayObject.java | 114 + .../unitycompat/system/MdArrayShort.java | 95 + .../unitycompat/system/MulticastDelegate.java | 27 + .../system/NotImplementedException.java | 40 + .../system/NotSupportedException.java | 40 + .../system/NullReferenceException.java | 40 + .../codename1/unitycompat/system/Object_.java | 65 + .../unitycompat/system/OverflowException.java | 40 + .../codename1/unitycompat/system/SByte_.java | 55 + .../codename1/unitycompat/system/Single_.java | 55 + .../codename1/unitycompat/system/String_.java | 395 ++ .../codename1/unitycompat/system/Struct.java | 40 + .../unitycompat/system/SystemException.java | 40 + .../codename1/unitycompat/system/Type.java | 151 + .../codename1/unitycompat/system/UInt16_.java | 55 + .../codename1/unitycompat/system/UInt32_.java | 55 + .../codename1/unitycompat/system/UInt64_.java | 55 + .../unitycompat/system/ValueTuple_2.java | 104 + .../unitycompat/system/ValueTuple_3.java | 109 + .../system/collections/IEnumerable.java | 29 + .../system/collections/IEnumerator.java | 33 + .../collections/generic/Dictionary_2.java | 466 +++ .../collections/generic/IEnumerable_1.java | 32 + .../collections/generic/IEnumerator_1.java | 31 + .../generic/KeyNotFoundException.java | 38 + .../collections/generic/KeyValuePair_2.java | 112 + .../system/collections/generic/List_1.java | 236 ++ .../unitycompat/system/linq/Enumerable.java | 294 ++ .../codename1/unitycompat/tmpro/TMP_Text.java | 198 + .../unitycompat/tmpro/TextMeshPro.java | 119 + .../unitycompat/tmpro/TextMeshProUGUI.java | 35 + .../unityengine/AnimationClip.java | 205 + .../unitycompat/unityengine/Animator.java | 767 ++++ .../unityengine/AnimatorStateInfo.java | 128 + .../unitycompat/unityengine/Application.java | 63 + .../unitycompat/unityengine/AudioClip.java | 74 + .../unityengine/AudioListener.java | 53 + .../unitycompat/unityengine/AudioOutput.java | 45 + .../unitycompat/unityengine/AudioSource.java | 316 ++ .../unitycompat/unityengine/Behaviour.java | 64 + .../unityengine/BoxCollider2D.java | 75 + .../unitycompat/unityengine/Camera.java | 200 + .../unitycompat/unityengine/Canvas.java | 116 + .../unityengine/CanvasRenderer.java | 35 + .../unityengine/CapsuleCollider2D.java | 82 + .../unityengine/CircleCollider2D.java | 54 + .../unitycompat/unityengine/Collider2D.java | 229 ++ .../unitycompat/unityengine/Collision2D.java | 92 + .../unitycompat/unityengine/Component.java | 150 + .../unityengine/CompositeCollider2D.java | 126 + .../unityengine/ContactFilter2D.java | 234 ++ .../unitycompat/unityengine/Coroutine.java | 36 + .../unitycompat/unityengine/Debug.java | 44 + .../unitycompat/unityengine/DrawCommand.java | 88 + .../unitycompat/unityengine/DrawList.java | 138 + .../unitycompat/unityengine/DrawView.java | 195 + .../unityengine/EdgeCollider2D.java | 90 + .../unitycompat/unityengine/Effector2D.java | 55 + .../unitycompat/unityengine/GameObject.java | 288 ++ .../unitycompat/unityengine/Gizmos.java | 65 + .../unitycompat/unityengine/Grid.java | 32 + .../unitycompat/unityengine/GridLayout.java | 141 + .../unitycompat/unityengine/Input.java | 758 ++++ .../unitycompat/unityengine/LayerMask.java | 156 + .../unityengine/MonoBehaviour.java | 164 + .../unitycompat/unityengine/Object.java | 163 + .../unityengine/ParticleSystem.java | 1435 +++++++ .../unityengine/ParticleSystemRenderer.java | 81 + .../unitycompat/unityengine/Physics2D.java | 725 ++++ .../unitycompat/unityengine/PhysicsQuery.java | 935 +++++ .../unitycompat/unityengine/PhysicsWorld.java | 1419 +++++++ .../unityengine/PlatformEffector2D.java | 164 + .../unitycompat/unityengine/PlayerPrefs.java | 165 + .../unityengine/PlayerPrefsStore.java | 51 + .../unityengine/PolygonCollider2D.java | 140 + .../unitycompat/unityengine/Random.java | 117 + .../unitycompat/unityengine/RaycastHit2D.java | 197 + .../unityengine/RectTransform.java | 197 + .../unitycompat/unityengine/Renderer.java | 70 + .../unitycompat/unityengine/Resources.java | 63 + .../unitycompat/unityengine/Rigidbody2D.java | 438 ++ .../RuntimeAnimatorController.java | 296 ++ .../unitycompat/unityengine/Screen.java | 42 + .../unityengine/ScriptableObject.java | 28 + .../unitycompat/unityengine/Sprite.java | 173 + .../unityengine/SpriteRenderer.java | 121 + .../unitycompat/unityengine/TextAsset.java | 158 + .../unitycompat/unityengine/Texture.java | 46 + .../unitycompat/unityengine/Texture2D.java | 32 + .../unitycompat/unityengine/TileOutline.java | 240 ++ .../unitycompat/unityengine/TilemapBase.java | 481 +++ .../unityengine/TilemapColliderBase.java | 253 ++ .../unitycompat/unityengine/Time.java | 101 + .../unitycompat/unityengine/Transform.java | 691 ++++ .../unitycompat/unityengine/UnityRuntime.java | 1777 ++++++++ .../unityengine/WaitForEndOfFrame.java | 28 + .../unityengine/WaitForFixedUpdate.java | 27 + .../unityengine/WaitForSeconds.java | 32 + .../unityengine/YieldInstruction.java | 28 + .../unityengine/assertions/Assert.java | 149 + .../unityengine/events/UnityAction.java | 51 + .../unityengine/events/UnityEvent.java | 57 + .../unityengine/events/UnityEventBase.java | 94 + .../unityengine/eventsystems/EventSystem.java | 68 + .../unityengine/eventsystems/UIBehaviour.java | 31 + .../scenemanagement/SceneManager.java | 79 + .../unityengine/tilemaps/Tile.java | 81 + .../unityengine/tilemaps/TileBase.java | 30 + .../unityengine/tilemaps/Tilemap.java | 75 + .../tilemaps/TilemapCollider2D.java | 41 + .../unityengine/tilemaps/TilemapRenderer.java | 65 + .../unitycompat/unityengine/ui/Button.java | 66 + .../unityengine/ui/CanvasScaler.java | 129 + .../unitycompat/unityengine/ui/Graphic.java | 131 + .../unityengine/ui/GraphicRaycaster.java | 38 + .../unitycompat/unityengine/ui/Image.java | 153 + .../unityengine/ui/MaskableGraphic.java | 28 + .../unityengine/ui/Selectable.java | 111 + .../unitycompat/unityengine/ui/Text.java | 145 + .../unityengine/ui/UnityGameAudio.java | 154 + .../unityengine/ui/UnityGamePrefs.java | 122 + .../unityengine/ui/UnityGameView.java | 920 +++++ .../NETStandard.Library.Ref/LICENSE.TXT | 23 + .../THIRD-PARTY-NOTICES.TXT | 631 +++ scripts/check-cast-semantics.sh | 20 +- scripts/unity-compat-samples/.gitignore | 3 + .../arcade2d/Assets/Prefabs/Shot.prefab | 70 + .../arcade2d/Assets/Prefabs/Shot.prefab.meta | 7 + .../arcade2d/Assets/Scenes/Arcade.unity | 444 ++ .../arcade2d/Assets/Scenes/Arcade.unity.meta | 7 + .../arcade2d/Assets/Scripts/Faller.cs | 41 + .../arcade2d/Assets/Scripts/Faller.cs.meta | 7 + .../arcade2d/Assets/Scripts/Player.cs | 91 + .../arcade2d/Assets/Scripts/Player.cs.meta | 7 + .../arcade2d/Assets/Scripts/Shot.cs | 53 + .../arcade2d/Assets/Scripts/Shot.cs.meta | 7 + .../arcade2d/Assets/Scripts/Target.cs | 57 + .../arcade2d/Assets/Scripts/Target.cs.meta | 7 + .../arcade2d/Assets/Sprites/sheet.png | Bin 0 -> 168 bytes .../arcade2d/Assets/Sprites/sheet.png.meta | 41 + .../ProjectSettings/EditorBuildSettings.asset | 10 + .../ProjectSettings/InputManager.asset | 23 + .../ProjectSettings/Physics2DSettings.asset | 10 + .../ProjectSettings/TimeManager.asset | 8 + .../arcade2d/expected-trace.txt | 67 + .../unity-compat-samples/arcade2d/input.txt | 10 + .../build-unity-project.sh | 133 + .../unity-compat-samples/console/.gitignore | 2 + .../console/Console.csproj | 31 + .../unity-compat-samples/console/Program.cs | 1252 ++++++ .../console/expected-output.txt | 126 + .../headless/java/headless/HeadlessMain.java | 287 ++ .../headless/java/headless/HeadlessTrace.java | 421 ++ .../headless/parparvm-trace.sh | 166 + .../host2d/Assets/Scenes/First.unity | 172 + .../host2d/Assets/Scenes/First.unity.meta | 7 + .../host2d/Assets/Scenes/Second.unity | 59 + .../host2d/Assets/Scenes/Second.unity.meta | 7 + .../host2d/Assets/Scripts/Host.cs | 34 + .../host2d/Assets/Scripts/Host.cs.meta | 7 + .../host2d/Assets/Scripts/Keeper.cs | 100 + .../host2d/Assets/Scripts/Keeper.cs.meta | 7 + .../host2d/Assets/Scripts/Marker.cs | 35 + .../host2d/Assets/Scripts/Marker.cs.meta | 7 + .../host2d/Assets/Scripts/Pad.cs | 37 + .../host2d/Assets/Scripts/Pad.cs.meta | 7 + .../ProjectSettings/EditorBuildSettings.asset | 13 + .../host2d/expected-trace.txt | 46 + .../host2d/expected-warnings.txt | 4 + scripts/unity-compat-samples/host2d/input.txt | 23 + .../host2d/java/host/SampleHost.java | 49 + .../menu2d/Assets/Audio/beep.wav | Bin 0 -> 844 bytes .../menu2d/Assets/Audio/beep.wav.meta | 10 + .../menu2d/Assets/Data/table.csv | 3 + .../menu2d/Assets/Data/table.csv.meta | 7 + .../Assets/Materials/Bouncy.physicsMaterial2D | 8 + .../Materials/Bouncy.physicsMaterial2D.meta | 8 + .../menu2d/Assets/Prefabs/Block.prefab | 44 + .../menu2d/Assets/Prefabs/Block.prefab.meta | 7 + .../menu2d/Assets/Resources/Icons/dot.png | Bin 0 -> 72 bytes .../Assets/Resources/Icons/dot.png.meta | 11 + .../menu2d/Assets/Resources/Levels/blob.bytes | Bin 0 -> 11 bytes .../Assets/Resources/Levels/blob.bytes.meta | 7 + .../menu2d/Assets/Resources/Levels/board.txt | 4 + .../Assets/Resources/Levels/board.txt.meta | 7 + .../menu2d/Assets/Resources/Levels/notes.rtf | 1 + .../Assets/Resources/Levels/notes.rtf.meta | 7 + .../menu2d/Assets/Scenes/Menu.unity | 770 ++++ .../menu2d/Assets/Scenes/Menu.unity.meta | 7 + .../menu2d/Assets/Scripts/Ball.cs | 52 + .../menu2d/Assets/Scripts/Ball.cs.meta | 7 + .../menu2d/Assets/Scripts/Board.cs | 120 + .../menu2d/Assets/Scripts/Board.cs.meta | 7 + .../menu2d/Assets/Scripts/Menu.cs | 124 + .../menu2d/Assets/Scripts/Menu.cs.meta | 7 + .../menu2d/Assets/Sprites/tile.png | Bin 0 -> 91 bytes .../menu2d/Assets/Sprites/tile.png.meta | 21 + .../menu2d/Assets/Sprites/wedge.png | Bin 0 -> 72 bytes .../menu2d/Assets/Sprites/wedge.png.meta | 20 + .../ProjectSettings/EditorBuildSettings.asset | 10 + .../menu2d/expected-trace.txt | 102 + scripts/unity-compat-samples/menu2d/input.txt | 11 + .../metadata-dump/.gitignore | 2 + .../metadata-dump/MetadataDump.csproj | 14 + .../metadata-dump/Program.cs | 225 + .../Assets/Animations/Hero.controller | 296 ++ .../Assets/Animations/Hero.controller.meta | 7 + .../platformer2d/Assets/Animations/Hurt.anim | 107 + .../Assets/Animations/Hurt.anim.meta | 7 + .../platformer2d/Assets/Animations/Idle.anim | 31 + .../Assets/Animations/Idle.anim.meta | 7 + .../platformer2d/Assets/Animations/Jump.anim | 29 + .../Assets/Animations/Jump.anim.meta | 7 + .../platformer2d/Assets/Animations/Proud.anim | 38 + .../Assets/Animations/Proud.anim.meta | 7 + .../platformer2d/Assets/Animations/Run.anim | 38 + .../Assets/Animations/Run.anim.meta | 7 + .../platformer2d/Assets/Materials/Dust.mat | 18 + .../Assets/Materials/Dust.mat.meta | 7 + .../Assets/Materials/Slick.physicsMaterial2D | 8 + .../Materials/Slick.physicsMaterial2D.meta | 7 + .../platformer2d/Assets/Scenes/Level.unity | 1225 ++++++ .../Assets/Scenes/Level.unity.meta | 7 + .../platformer2d/Assets/Scripts/Hero.cs | 197 + .../platformer2d/Assets/Scripts/Hero.cs.meta | 7 + .../platformer2d/Assets/Scripts/Hopper.cs | 60 + .../Assets/Scripts/Hopper.cs.meta | 7 + .../platformer2d/Assets/Sprites/dot.png | Bin 0 -> 80 bytes .../platformer2d/Assets/Sprites/dot.png.meta | 18 + .../platformer2d/Assets/Sprites/hero.png | Bin 0 -> 135 bytes .../platformer2d/Assets/Sprites/hero.png.meta | 65 + .../platformer2d/Assets/Sprites/tiles.png | Bin 0 -> 107 bytes .../Assets/Sprites/tiles.png.meta | 41 + .../platformer2d/Assets/Tiles/Ground.asset | 33 + .../Assets/Tiles/Ground.asset.meta | 7 + .../platformer2d/Assets/Tiles/Ledge.asset | 33 + .../Assets/Tiles/Ledge.asset.meta | 7 + .../ProjectSettings/EditorBuildSettings.asset | 10 + .../ProjectSettings/InputManager.asset | 23 + .../ProjectSettings/Physics2DSettings.asset | 10 + .../ProjectSettings/TimeManager.asset | 8 + .../platformer2d/expected-trace.txt | 107 + .../platformer2d/input.txt | 6 + .../player/java/player/UnityPlayer.java | 145 + .../java/player/UnityPlayerDesktop.java | 124 + .../player/offscreen-check.sh | 55 + .../impl/javase/OffscreenSurface.java | 56 + .../testing/OffscreenImplementation.java | 291 ++ .../java/offscreen/OffscreenCheck.java | 603 +++ .../impl/javase/JavaSEJoglSurface.java | 36 + .../query2d/Assets/Scenes/Query.unity | 370 ++ .../query2d/Assets/Scenes/Query.unity.meta | 7 + .../query2d/Assets/Scripts/Fingers.cs | 84 + .../query2d/Assets/Scripts/Fingers.cs.meta | 7 + .../query2d/Assets/Scripts/Probe.cs | 272 ++ .../query2d/Assets/Scripts/Probe.cs.meta | 7 + .../ProjectSettings/EditorBuildSettings.asset | 10 + .../ProjectSettings/InputManager.asset | 41 + .../ProjectSettings/Physics2DSettings.asset | 13 + .../query2d/ProjectSettings/TagManager.asset | 43 + .../query2d/ProjectSettings/TimeManager.asset | 8 + .../query2d/expected-trace.txt | 138 + .../unity-compat-samples/query2d/input.txt | 27 + .../unity-compat-samples/run-unity-project.sh | 168 + .../scene2d/Assets/Scenes/Main.unity | 210 + .../scene2d/Assets/Scripts/BallController.cs | 124 + .../Assets/Scripts/BallController.cs.meta | 11 + .../scene2d/Assets/Scripts/Spinner.cs | 68 + .../scene2d/Assets/Scripts/Spinner.cs.meta | 11 + .../scene2d/Assets/Sprites/ball.png | Bin 0 -> 152 bytes .../scene2d/Assets/Sprites/ball.png.meta | 12 + .../scene2d/Assets/Sprites/crate.png | Bin 0 -> 146 bytes .../scene2d/Assets/Sprites/crate.png.meta | 12 + .../scene2d/Scene2d.csproj | 25 + .../scene2d/expected-trace.txt | 23 + .../scene2d/java/scene/SceneMain.java | 87 + .../unity-compat-samples/structbench/Bench.cs | 108 + .../structbench/StructBench.csproj | 25 + .../structbench/java/bench/BenchMain.java | 84 + .../structbench/java/bench/MutableVec2.java | 66 + .../structbench/java/bench/OutParam.java | 96 + .../structbench/java/bench/Scalar.java | 62 + vm/ByteCodeTranslator/src/cn1_globals.m | 12 +- .../tools/translator/ByteCodeTranslator.java | 12 +- .../translator/JavascriptMethodGenerator.java | 167 +- .../src/javascript/parparvm_runtime.js | 20 +- vm/ByteCodeTranslator/src/nativeMethods.m | 4 +- .../template.xcodeproj/project.pbxproj | 6 +- .../template.xcodeproj/project.pbxproj | 6 +- vm/backend/build.sh | 2 +- vm/backend/docker/link.sh | 2 +- vm/benchmarks/README.md | 2 +- vm/benchmarks/translate-and-build.sh | 2 +- vm/selfhost/compile-dist.sh | 4 +- vm/selfhost/experiments/README.md | 2 +- .../JavascriptRuntimeSemanticsTest.java | 302 ++ ...PrimitiveArrayOfArraysIntegrationTest.java | 127 + .../translator/JsFloatCompareOnceApp.java | 138 + .../tools/translator/JsFloatRoundingApp.java | 113 + .../translator/JsMultiArrayDimensionsApp.java | 95 + .../tools/translator/JsStackSwapApp.java | 170 + .../translator/JsWideStackShuffleApp.java | 97 + 447 files changed, 64651 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/unity-compat.yml create mode 100644 docs/demos/common/src/main/snippets/developer-guide/unity-interop.cs create mode 100644 docs/demos/common/src/main/snippets/developer-guide/unity-interop.sh create mode 100644 docs/demos/common/src/main/snippets/developer-guide/unity-interop.txt create mode 100644 docs/developer-guide/Unity-Interop.asciidoc create mode 100644 docs/developer-guide/img/unity-interop-asteroids.png create mode 100644 docs/developer-guide/img/unity-interop-fruitopia-level.png create mode 100644 docs/developer-guide/img/unity-interop-fruitopia-menu.png create mode 100644 docs/developer-guide/img/unity-interop-fruitopia.png create mode 100644 docs/developer-guide/img/unity-interop-pong.png create mode 100644 docs/developer-guide/img/unity-interop-snake.png create mode 100644 docs/developer-guide/img/unity-interop-sokoban-complete.png create mode 100644 docs/developer-guide/img/unity-interop-sokoban.png create mode 100644 maven/build-engine/src/main/java/com/codename1/maven/UnityProjectBuilder.java create mode 100644 maven/build-engine/src/main/java/com/codename1/maven/UnityProjectImporter.java create mode 100644 maven/build-engine/src/test/java/com/codename1/maven/UnityProjectBuilderTest.java create mode 100644 maven/build-engine/src/test/java/com/codename1/maven/UnityProjectImporterTest.java create mode 100644 maven/cil-translator/pom.xml create mode 100644 maven/cil-translator/spotbugs-exclude.xml create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilAssembly.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilFormatException.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilType.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/MetadataReader.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/MethodBody.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/PortablePdb.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/SignatureParser.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/metadata/Universe.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/tools/MetadataDump.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/Jvm.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/MethodTranslator.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/Names.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/RuntimeIndex.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/TranslationException.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/Translator.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/TypeMapping.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/cil/translate/Val.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/unity/scenecompiler/AnimationAssets.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/unity/scenecompiler/ParticleAssets.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/unity/scenecompiler/SceneCompiler.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/unity/scenecompiler/TilemapAssets.java create mode 100644 maven/cil-translator/src/main/java/com/codename1/unity/scenecompiler/UnityYaml.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/cil/UnityToolchain.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/cil/tools/MetadataReaderOracleTest.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/cil/translate/ConsoleSampleTest.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/cil/translate/DiagnosticsTest.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/unity/scenecompiler/AssetParsersTest.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/unity/scenecompiler/ResourcesTest.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/unity/scenecompiler/Scene2dSampleTest.java create mode 100644 maven/cil-translator/src/test/java/com/codename1/unity/scenecompiler/UnityYamlTest.java create mode 100644 maven/codenameone-gradle-plugin/src/main/java/com/codename1/gradle/tasks/CompileUnityTask.java create mode 100644 maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CompileUnityMojo.java create mode 100644 maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ImportUnityProjectMojo.java create mode 100644 maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/UnityPomUpdater.java create mode 100644 maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/CompileUnityMojoTest.java create mode 100644 maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/UnityPomUpdaterTest.java create mode 100644 maven/core-unittests/src/test/java/com/codename1/gaming/physics/box2d/collision/broadphase/BroadPhasePairSortTest.java create mode 100755 maven/integration-tests/unity-compat-project-test.sh create mode 100755 maven/integration-tests/unity-compat-test.sh create mode 100644 maven/unity-compat/pom.xml create mode 100644 maven/unity-compat/spotbugs-exclude.xml create mode 100644 maven/unity-compat/src/main/csharp/.gitignore create mode 100644 maven/unity-compat/src/main/csharp/UnityEngine.Values/UnityEngine.Values.csproj create mode 100644 maven/unity-compat/src/main/csharp/UnityEngine.Values/Values.cs create mode 100644 maven/unity-compat/src/main/csharp/UnityEngine/Engine.cs create mode 100644 maven/unity-compat/src/main/csharp/UnityEngine/UnityEngine.csproj create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/app/UnityApplication.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/app/package-info.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/cinemachine/CinemachineBrain.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/cinemachine/CinemachineComponentBase.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/cinemachine/CinemachineFramingTransposer.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/cinemachine/CinemachineTransposer.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/cinemachine/CinemachineVirtualCamera.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/cinemachine/CinemachineVirtualCameraBase.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/cinemachine/LensSettings.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Action.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Action_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Action_2.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Action_3.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/ArgumentException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/ArgumentNullException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/ArgumentOutOfRangeException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/ArithmeticException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/ArrayView.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Array_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Boolean_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Byte_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Char_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Console.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Delegate.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/DivideByZeroException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Double_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/EnumBox.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Enum_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Environment.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/EventArgs.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/EventHandler.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/EventHandler_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Exception.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/FormatException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Func_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Func_2.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Func_3.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Func_4.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/IComparable.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/IComparable_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/IDisposable.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/IEquatable_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/IndexOutOfRangeException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Int16_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Int32_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Int64_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Interop.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/InvalidCastException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/InvalidOperationException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Math.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArray.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayBoolean.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayByte.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayChar.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayDouble.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayFloat.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayInt.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayLong.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayObject.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MdArrayShort.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/MulticastDelegate.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/NotImplementedException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/NotSupportedException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/NullReferenceException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Object_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/OverflowException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/SByte_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Single_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/String_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Struct.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/SystemException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Type.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/UInt16_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/UInt32_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/UInt64_.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/ValueTuple_2.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/ValueTuple_3.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/IEnumerable.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/IEnumerator.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/generic/Dictionary_2.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/generic/IEnumerable_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/generic/IEnumerator_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/generic/KeyNotFoundException.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/generic/KeyValuePair_2.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/collections/generic/List_1.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/system/linq/Enumerable.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/tmpro/TMP_Text.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/tmpro/TextMeshPro.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/tmpro/TextMeshProUGUI.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/AnimationClip.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Animator.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/AnimatorStateInfo.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Application.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/AudioClip.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/AudioListener.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/AudioOutput.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/AudioSource.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Behaviour.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/BoxCollider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Camera.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Canvas.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/CanvasRenderer.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/CapsuleCollider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/CircleCollider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Collider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Collision2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Component.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/CompositeCollider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ContactFilter2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Coroutine.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Debug.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/DrawCommand.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/DrawList.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/DrawView.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/EdgeCollider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Effector2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/GameObject.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Gizmos.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Grid.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/GridLayout.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Input.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/LayerMask.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/MonoBehaviour.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Object.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ParticleSystem.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ParticleSystemRenderer.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Physics2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/PhysicsQuery.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/PhysicsWorld.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/PlatformEffector2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/PlayerPrefs.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/PlayerPrefsStore.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/PolygonCollider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Random.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/RaycastHit2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/RectTransform.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Renderer.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Resources.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Rigidbody2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/RuntimeAnimatorController.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Screen.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ScriptableObject.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Sprite.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/SpriteRenderer.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/TextAsset.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Texture.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Texture2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/TileOutline.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/TilemapBase.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/TilemapColliderBase.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Time.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/Transform.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/UnityRuntime.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/WaitForEndOfFrame.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/WaitForFixedUpdate.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/WaitForSeconds.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/YieldInstruction.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/assertions/Assert.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/events/UnityAction.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/events/UnityEvent.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/events/UnityEventBase.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/eventsystems/EventSystem.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/eventsystems/UIBehaviour.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/scenemanagement/SceneManager.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/tilemaps/Tile.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/tilemaps/TileBase.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/tilemaps/Tilemap.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/tilemaps/TilemapCollider2D.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/tilemaps/TilemapRenderer.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/Button.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/CanvasScaler.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/Graphic.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/GraphicRaycaster.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/Image.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/MaskableGraphic.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/Selectable.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/Text.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/UnityGameAudio.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/UnityGamePrefs.java create mode 100644 maven/unity-compat/src/main/java/com/codename1/unitycompat/unityengine/ui/UnityGameView.java create mode 100755 maven/unity-compat/third-party/NETStandard.Library.Ref/LICENSE.TXT create mode 100755 maven/unity-compat/third-party/NETStandard.Library.Ref/THIRD-PARTY-NOTICES.TXT create mode 100644 scripts/unity-compat-samples/.gitignore create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Prefabs/Shot.prefab create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Prefabs/Shot.prefab.meta create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scenes/Arcade.unity create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scenes/Arcade.unity.meta create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Faller.cs create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Faller.cs.meta create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Player.cs create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Player.cs.meta create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Shot.cs create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Shot.cs.meta create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Target.cs create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Scripts/Target.cs.meta create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Sprites/sheet.png create mode 100644 scripts/unity-compat-samples/arcade2d/Assets/Sprites/sheet.png.meta create mode 100644 scripts/unity-compat-samples/arcade2d/ProjectSettings/EditorBuildSettings.asset create mode 100644 scripts/unity-compat-samples/arcade2d/ProjectSettings/InputManager.asset create mode 100644 scripts/unity-compat-samples/arcade2d/ProjectSettings/Physics2DSettings.asset create mode 100644 scripts/unity-compat-samples/arcade2d/ProjectSettings/TimeManager.asset create mode 100644 scripts/unity-compat-samples/arcade2d/expected-trace.txt create mode 100644 scripts/unity-compat-samples/arcade2d/input.txt create mode 100755 scripts/unity-compat-samples/build-unity-project.sh create mode 100644 scripts/unity-compat-samples/console/.gitignore create mode 100644 scripts/unity-compat-samples/console/Console.csproj create mode 100644 scripts/unity-compat-samples/console/Program.cs create mode 100644 scripts/unity-compat-samples/console/expected-output.txt create mode 100644 scripts/unity-compat-samples/headless/java/headless/HeadlessMain.java create mode 100644 scripts/unity-compat-samples/headless/java/headless/HeadlessTrace.java create mode 100755 scripts/unity-compat-samples/headless/parparvm-trace.sh create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scenes/First.unity create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scenes/First.unity.meta create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scenes/Second.unity create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scenes/Second.unity.meta create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Host.cs create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Host.cs.meta create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Keeper.cs create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Keeper.cs.meta create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Marker.cs create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Marker.cs.meta create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Pad.cs create mode 100644 scripts/unity-compat-samples/host2d/Assets/Scripts/Pad.cs.meta create mode 100644 scripts/unity-compat-samples/host2d/ProjectSettings/EditorBuildSettings.asset create mode 100644 scripts/unity-compat-samples/host2d/expected-trace.txt create mode 100644 scripts/unity-compat-samples/host2d/expected-warnings.txt create mode 100644 scripts/unity-compat-samples/host2d/input.txt create mode 100644 scripts/unity-compat-samples/host2d/java/host/SampleHost.java create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Audio/beep.wav create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Audio/beep.wav.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Data/table.csv create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Data/table.csv.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Materials/Bouncy.physicsMaterial2D create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Materials/Bouncy.physicsMaterial2D.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Prefabs/Block.prefab create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Prefabs/Block.prefab.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Icons/dot.png create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Icons/dot.png.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Levels/blob.bytes create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Levels/blob.bytes.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Levels/board.txt create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Levels/board.txt.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Levels/notes.rtf create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Resources/Levels/notes.rtf.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scenes/Menu.unity create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scenes/Menu.unity.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scripts/Ball.cs create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scripts/Ball.cs.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scripts/Board.cs create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scripts/Board.cs.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scripts/Menu.cs create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Scripts/Menu.cs.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Sprites/tile.png create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Sprites/tile.png.meta create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Sprites/wedge.png create mode 100644 scripts/unity-compat-samples/menu2d/Assets/Sprites/wedge.png.meta create mode 100644 scripts/unity-compat-samples/menu2d/ProjectSettings/EditorBuildSettings.asset create mode 100644 scripts/unity-compat-samples/menu2d/expected-trace.txt create mode 100644 scripts/unity-compat-samples/menu2d/input.txt create mode 100644 scripts/unity-compat-samples/metadata-dump/.gitignore create mode 100644 scripts/unity-compat-samples/metadata-dump/MetadataDump.csproj create mode 100644 scripts/unity-compat-samples/metadata-dump/Program.cs create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Hero.controller create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Hero.controller.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Hurt.anim create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Hurt.anim.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Idle.anim create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Idle.anim.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Jump.anim create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Jump.anim.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Proud.anim create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Proud.anim.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Run.anim create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Animations/Run.anim.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Materials/Dust.mat create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Materials/Dust.mat.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Materials/Slick.physicsMaterial2D create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Materials/Slick.physicsMaterial2D.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Scenes/Level.unity create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Scenes/Level.unity.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Scripts/Hero.cs create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Scripts/Hero.cs.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Scripts/Hopper.cs create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Scripts/Hopper.cs.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Sprites/dot.png create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Sprites/dot.png.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Sprites/hero.png create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Sprites/hero.png.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Sprites/tiles.png create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Sprites/tiles.png.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Tiles/Ground.asset create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Tiles/Ground.asset.meta create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Tiles/Ledge.asset create mode 100644 scripts/unity-compat-samples/platformer2d/Assets/Tiles/Ledge.asset.meta create mode 100644 scripts/unity-compat-samples/platformer2d/ProjectSettings/EditorBuildSettings.asset create mode 100644 scripts/unity-compat-samples/platformer2d/ProjectSettings/InputManager.asset create mode 100644 scripts/unity-compat-samples/platformer2d/ProjectSettings/Physics2DSettings.asset create mode 100644 scripts/unity-compat-samples/platformer2d/ProjectSettings/TimeManager.asset create mode 100644 scripts/unity-compat-samples/platformer2d/expected-trace.txt create mode 100644 scripts/unity-compat-samples/platformer2d/input.txt create mode 100644 scripts/unity-compat-samples/player/java/player/UnityPlayer.java create mode 100644 scripts/unity-compat-samples/player/java/player/UnityPlayerDesktop.java create mode 100755 scripts/unity-compat-samples/player/offscreen-check.sh create mode 100644 scripts/unity-compat-samples/player/offscreen/java/com/codename1/impl/javase/OffscreenSurface.java create mode 100644 scripts/unity-compat-samples/player/offscreen/java/com/codename1/testing/OffscreenImplementation.java create mode 100644 scripts/unity-compat-samples/player/offscreen/java/offscreen/OffscreenCheck.java create mode 100644 scripts/unity-compat-samples/player/software/com/codename1/impl/javase/JavaSEJoglSurface.java create mode 100644 scripts/unity-compat-samples/query2d/Assets/Scenes/Query.unity create mode 100644 scripts/unity-compat-samples/query2d/Assets/Scenes/Query.unity.meta create mode 100644 scripts/unity-compat-samples/query2d/Assets/Scripts/Fingers.cs create mode 100644 scripts/unity-compat-samples/query2d/Assets/Scripts/Fingers.cs.meta create mode 100644 scripts/unity-compat-samples/query2d/Assets/Scripts/Probe.cs create mode 100644 scripts/unity-compat-samples/query2d/Assets/Scripts/Probe.cs.meta create mode 100644 scripts/unity-compat-samples/query2d/ProjectSettings/EditorBuildSettings.asset create mode 100644 scripts/unity-compat-samples/query2d/ProjectSettings/InputManager.asset create mode 100644 scripts/unity-compat-samples/query2d/ProjectSettings/Physics2DSettings.asset create mode 100644 scripts/unity-compat-samples/query2d/ProjectSettings/TagManager.asset create mode 100644 scripts/unity-compat-samples/query2d/ProjectSettings/TimeManager.asset create mode 100644 scripts/unity-compat-samples/query2d/expected-trace.txt create mode 100644 scripts/unity-compat-samples/query2d/input.txt create mode 100755 scripts/unity-compat-samples/run-unity-project.sh create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Scenes/Main.unity create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Scripts/BallController.cs create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Scripts/BallController.cs.meta create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Scripts/Spinner.cs create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Scripts/Spinner.cs.meta create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Sprites/ball.png create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Sprites/ball.png.meta create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Sprites/crate.png create mode 100644 scripts/unity-compat-samples/scene2d/Assets/Sprites/crate.png.meta create mode 100644 scripts/unity-compat-samples/scene2d/Scene2d.csproj create mode 100644 scripts/unity-compat-samples/scene2d/expected-trace.txt create mode 100644 scripts/unity-compat-samples/scene2d/java/scene/SceneMain.java create mode 100644 scripts/unity-compat-samples/structbench/Bench.cs create mode 100644 scripts/unity-compat-samples/structbench/StructBench.csproj create mode 100644 scripts/unity-compat-samples/structbench/java/bench/BenchMain.java create mode 100644 scripts/unity-compat-samples/structbench/java/bench/MutableVec2.java create mode 100644 scripts/unity-compat-samples/structbench/java/bench/OutParam.java create mode 100644 scripts/unity-compat-samples/structbench/java/bench/Scalar.java create mode 100644 vm/tests/src/test/java/com/codename1/tools/translator/PrimitiveArrayOfArraysIntegrationTest.java create mode 100644 vm/tests/src/test/resources/com/codename1/tools/translator/JsFloatCompareOnceApp.java create mode 100644 vm/tests/src/test/resources/com/codename1/tools/translator/JsFloatRoundingApp.java create mode 100644 vm/tests/src/test/resources/com/codename1/tools/translator/JsMultiArrayDimensionsApp.java create mode 100644 vm/tests/src/test/resources/com/codename1/tools/translator/JsStackSwapApp.java create mode 100644 vm/tests/src/test/resources/com/codename1/tools/translator/JsWideStackShuffleApp.java diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 896118dd33e..fb91faffbfb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,14 @@ jobs: R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} R2_BUCKET: ${{ vars.R2_BUCKET }} R2_BASE_URL: https://repo.codenameone.com/maven2 + # The .NET SDK, which the core reactor needs for codenameone-unity-compat (see + # "Set up .NET" below). Its first-run state and the NuGet package cache stay in + # the workspace rather than the runner's home directory, so the one package the + # build restores is visibly this job's and nothing outlives it. + DOTNET_CLI_HOME: ${{ github.workspace }}/.dotnet/cli-home + NUGET_PACKAGES: ${{ github.workspace }}/.nuget-packages + DOTNET_CLI_TELEMETRY_OPTOUT: '1' + DOTNET_NOLOGO: '1' steps: - uses: actions/checkout@v6 @@ -63,9 +71,40 @@ jobs: git config --global user.name "GitHub Actions Bot" git config --global user.email "github-actions@codenameone.com" + # codenameone-cil-translator and codenameone-unity-compat are published by the + # core reactor below, at the release's own version: the archetype's poms and + # both build plugins resolve them at that version, so a release without them + # is one whose generated projects cannot build a Unity source tree. The + # runtime's build compiles two C# projects, hence the SDK. Both target + # netstandard2.1, which any current SDK builds; the channel is the one + # unity-compat.yml tests the same build with, so the release compiles the C# + # with the compiler CI exercised. + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Check Maven Version run: mvn -v + - name: Require the .NET SDK + # Not continue-on-error, for the reason the frozen-artifact check below is + # not: nothing has been built yet, so there is nothing to salvage. Without + # this the same absence surfaces an hour into the reactor, as exec-maven-plugin + # failing to start a program in a module nobody was looking at. + run: | + if ! command -v dotnet >/dev/null 2>&1; then + echo "The .NET SDK is not on the PATH. The release builds" >&2 + echo "codenameone-unity-compat, which compiles C# with it." >&2 + exit 1 + fi + if [ -z "$(dotnet --list-sdks)" ]; then + echo "dotnet is on the PATH but no SDK is installed (a runtime alone" >&2 + echo "cannot build). The release builds codenameone-unity-compat with it." >&2 + exit 1 + fi + dotnet --version + - name: Verify frozen artifacts are on R2 # A release resolves codenameone-designer at its pinned version but does not # publish it ( in maven/pom.xml). While Central was in every @@ -99,8 +138,14 @@ jobs: # this same reactor, so at that moment it exists only in the local # repo and no remote can serve it. -DskipTests covers the matching # surefire pass. + # -Dunity-compat: codenameone-cil-translator and codenameone-unity-compat + # sit in a profile, because building the second needs the .NET SDK and + # an ordinary framework build must not. A release does publish them, in + # this pass and at this version, so the profile is on here. Leaving the + # flag off does not fail the build -- the two modules simply drop out + # of the reactor -- which is why "Confirm artifacts on R2" names them. xvfb-run -a mvn deploy -Psign-artifacts -Dgpg.passphrase=$MAVEN_GPG_PASSPHRASE \ - -Plocal-dev-javase -Darchetype.test.skip=true -DskipTests \ + -Plocal-dev-javase -Dunity-compat -Darchetype.test.skip=true -DskipTests \ -DskipPublishing=true env: MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} @@ -138,6 +183,7 @@ jobs: set -e for artifact in codenameone-core codenameone-maven-plugin codenameone-platform-feature-catalog \ codenameone-build-hint-catalog codenameone-build-hint-tools \ + codenameone-cil-translator codenameone-unity-compat \ cn1app-archetype cn1lib-archetype; do url="${R2_BASE_URL}/com/codenameone/${artifact}/${GITHUB_REF_NAME}/${artifact}-${GITHUB_REF_NAME}.pom?cb=${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" code=$(curl -s -o /dev/null -w "%{http_code}" "$url") @@ -147,6 +193,22 @@ jobs: fi echo "ok: $artifact ${GITHUB_REF_NAME}" done + # A pom says the module was staged, not that its jars were. These three are + # resolved by name during an application's build -- the runtime merged into + # the application, the translator forked as a tool, and the "references" + # classifier its C# compiles against -- and the last is an attachment no + # pom mentions, so only asking for the file itself notices it missing. + for file in codenameone-cil-translator/${GITHUB_REF_NAME}/codenameone-cil-translator-${GITHUB_REF_NAME}.jar \ + codenameone-unity-compat/${GITHUB_REF_NAME}/codenameone-unity-compat-${GITHUB_REF_NAME}.jar \ + codenameone-unity-compat/${GITHUB_REF_NAME}/codenameone-unity-compat-${GITHUB_REF_NAME}-references.jar; do + url="${R2_BASE_URL}/com/codenameone/${file}?cb=${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + code=$(curl -s -o /dev/null -w "%{http_code}" "$url") + if [ "$code" != "200" ]; then + echo "MISSING on R2: $file (HTTP $code)" >&2 + exit 1 + fi + echo "ok: $file" + done # --- Game Builder editor ------------------------------------------------- # The cn1:gamebuilder goal resolves @@ -379,7 +441,7 @@ jobs: # poll, not the deploy, was the verdict, and the marker could not trust it. With # skipPublishing there is no upload to misreport: a build that fails, fails, and # the tree it staged may be missing whichever modules came after the failure. - # r2_core_confirm only covers seven key artifacts, so without this a build that + # r2_core_confirm only covers nine key artifacts, so without this a build that # died late could publish and advertise a version that is quietly incomplete. if: >- always() && steps.deploy.outcome == 'success' && diff --git a/.github/workflows/unity-compat.yml b/.github/workflows/unity-compat.yml new file mode 100644 index 00000000000..92cc4821346 --- /dev/null +++ b/.github/workflows/unity-compat.yml @@ -0,0 +1,249 @@ +name: Unity compatibility + +# Unity 2D projects built as Codename One applications: the CIL translator and +# scene compiler (maven/cil-translator) and the runtime translated scripts run +# against (maven/unity-compat). Both live behind the unity-compat Maven profile, +# because building them needs the .NET SDK and the framework build must not. +# +# unit tests the metadata reader against .NET's own +# System.Reflection.Metadata, the console sample's +# output against `dotnet run`, and the 2D scene's +# trace; -Dunity.requireDotnet=true turns "no .NET +# SDK, skipped" into a failure +# unity-compat-test.sh every sample under scripts/unity-compat-samples +# run on the JVM, through ParparVM's C target as a +# native executable and through its JavaScript +# target under Node, all traces identical +# unity-compat-project-test.sh +# the build integration: a sample imported into an +# archetype application with cn1:import-unity-project, +# built by the application's own Maven build and +# staged for every target, then converted to +# Gradle and staged again: the same entries +# static analysis SpotBugs (zero findings), PMD (the forbidden +# list) and Checkstyle (zero errors) over both +# modules, enforced by the script PR CI uses for +# the rest of the tree, and the ClassCastException +# gate over the runtime, which ParparVM translates +# +# The static analysis gates run HERE and not in pr.yml's Java 8 leg, where the +# other modules are judged. Both modules build only with the .NET SDK, and +# putting them in that leg would make every pull request in the repository +# install .NET and build C# to analyse code it did not touch. Nothing is lost +# by keeping them apart: the `paths` below name both modules, the rulesets and +# the scripts that do the judging, so no change to any of them can merge +# without this workflow running, and the report script fails on a module that +# produced no report rather than reading the absence as clean. +on: + pull_request: + branches: [ master ] + paths: + - 'maven/cil-translator/**' + - 'maven/unity-compat/**' + - 'maven/pom.xml' + - 'maven/integration-tests/unity-compat-test.sh' + - 'maven/integration-tests/unity-compat-project-test.sh' + - 'maven/build-engine/src/main/java/com/codename1/maven/Unity*' + - 'maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/*Unity*' + - 'maven/codenameone-gradle-plugin/src/main/java/com/codename1/gradle/AppSupport.java' + - 'maven/codenameone-gradle-plugin/src/main/java/com/codename1/gradle/tasks/CompileUnityTask.java' + - 'maven/build-engine/src/main/java/com/codename1/maven/GradleConversion.java' + - 'maven/cn1app-archetype/**' + - 'maven/integration-tests/inc/**' + - 'scripts/unity-compat-samples/**' + - 'CodenameOne/src/com/codename1/gaming/**' + - 'vm/ByteCodeTranslator/**' + - 'vm/JavaAPI/**' + - 'vm/selfhost/js/**' + # What judges the two modules: a change that weakens a gate has to run it. + - '.github/scripts/generate-quality-report.py' + - 'maven/core-unittests/pmd.xml' + - 'maven/core-unittests/checkstyle.xml' + - 'scripts/check-cast-semantics.sh' + - 'scripts/cast-semantics-baseline.txt' + - '.github/workflows/unity-compat.yml' + push: + branches: [ master ] + paths: + - 'maven/cil-translator/**' + - 'maven/unity-compat/**' + - 'maven/pom.xml' + - 'maven/integration-tests/unity-compat-test.sh' + - 'maven/integration-tests/unity-compat-project-test.sh' + - 'maven/build-engine/src/main/java/com/codename1/maven/Unity*' + - 'maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/*Unity*' + - 'maven/codenameone-gradle-plugin/src/main/java/com/codename1/gradle/AppSupport.java' + - 'maven/codenameone-gradle-plugin/src/main/java/com/codename1/gradle/tasks/CompileUnityTask.java' + - 'maven/build-engine/src/main/java/com/codename1/maven/GradleConversion.java' + - 'maven/cn1app-archetype/**' + - 'maven/integration-tests/inc/**' + - 'scripts/unity-compat-samples/**' + - 'CodenameOne/src/com/codename1/gaming/**' + - 'vm/ByteCodeTranslator/**' + - 'vm/JavaAPI/**' + - 'vm/selfhost/js/**' + # What judges the two modules: a change that weakens a gate has to run it. + - '.github/scripts/generate-quality-report.py' + - 'maven/core-unittests/pmd.xml' + - 'maven/core-unittests/checkstyle.xml' + - 'scripts/check-cast-semantics.sh' + - 'scripts/cast-semantics-baseline.txt' + - '.github/workflows/unity-compat.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + unity-compat: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + env: + DOTNET_CLI_TELEMETRY_OPTOUT: '1' + DOTNET_NOLOGO: '1' + steps: + - uses: actions/checkout@v6 + - name: Set up JDK 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'temurin' + cache: 'maven' + # The scripts and the runtime's C# target netstandard2.1, which any SDK + # builds, but the console sample is also built for net10.0 (that build is + # the `dotnet run` oracle) and the metadata dumper is a net10.0 program. + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: '20' + # xvfb as in the other Maven builds; clang for the native executables. + - name: Install xvfb and clang + run: bash scripts/ci/apt-get-install.sh xvfb clang + - name: Build the core, the translator and the runtime + env: + RETRY_ONLY_MATCHING: transient + RETRY_ATTEMPTS: '5' + RETRY_DELAY_SECONDS: '60' + run: | + cd maven + bash ../scripts/ci/retry.sh xvfb-run -a mvn -B install -Plocal-dev-javase -Dunity-compat -DskipTests -Darchetype.test.skip=true -Dmaven.javadoc.skip=true -pl cil-translator,unity-compat -am + - name: Unit tests + working-directory: maven + run: xvfb-run -a mvn -B -Plocal-dev-javase -Dunity-compat -Dunity.requireDotnet=true -pl cil-translator,unity-compat test + # `verify` writes the three reports of each module and fails on its own + # for SpotBugs and Checkstyle. The reports are deleted first so that the + # step after this one reads this run's answer and never a cached one. + - name: Static analysis of the translator and the runtime + working-directory: maven + run: | + rm -f cil-translator/target/spotbugsXml.xml cil-translator/target/pmd.xml cil-translator/target/checkstyle-result.xml + rm -f unity-compat/target/spotbugsXml.xml unity-compat/target/pmd.xml unity-compat/target/checkstyle-result.xml + xvfb-run -a mvn -B -Plocal-dev-javase -Dunity-compat -DskipTests -pl cil-translator,unity-compat verify + # The same script, with the same forbidden PMD list, that judges the rest + # of the tree in pr.yml. PMD only writes a report, so this step is what + # enforces it; every module named must have produced each report. + - name: Enforce the quality gates + if: ${{ !cancelled() }} + env: + QUALITY_REPORT_TARGET_DIRS: maven/cil-translator/target:maven/unity-compat/target + QUALITY_REPORT_REQUIRED_SPOTBUGS: cil-translator:unity-compat + QUALITY_REPORT_REQUIRED_PMD: cil-translator:unity-compat + QUALITY_REPORT_REQUIRED_CHECKSTYLE: cil-translator:unity-compat + QUALITY_REPORT_TITLE: Unity compatibility quality report + run: python3 .github/scripts/generate-quality-report.py + - name: Build ParparVM's class library and translator + run: | + bash scripts/ci/retry.sh mvn -B -f vm/JavaAPI/pom.xml package -DskipTests + bash scripts/ci/retry.sh mvn -B -f vm/ByteCodeTranslator/pom.xml package -DskipTests + # The runtime and the classes translated from C# are translated again by + # ParparVM, whose CHECKCAST does not throw. Named explicitly: the default + # roots belong to pr.yml, which builds them. + - name: No reliance on ClassCastException in the runtime + run: | + test -d maven/unity-compat/target/classes/UnityEngine + scripts/check-cast-semantics.sh maven/unity-compat/target/classes + - name: Samples on the JVM, native and JavaScript + working-directory: maven/integration-tests + run: bash unity-compat-test.sh --parparvm + - name: Upload the samples' logs and traces + if: failure() + uses: actions/upload-artifact@v7 + with: + name: unity-compat-logs + path: | + maven/integration-tests/build/unity-compat/**/*.log + maven/integration-tests/build/unity-compat/**/*.txt + maven/integration-tests/build/unity-compat/**/*.err + maven/integration-tests/build/unity-compat/**/*.diff + maven/cil-translator/target/unity-tests/**/*.txt + maven/cil-translator/target/surefire-reports/** + if-no-files-found: ignore + + # The build integration needs the whole reactor -- the Maven plugin and the + # archetype, not just the translator and the runtime -- so it is a job of its + # own rather than a longer build in front of the samples. + unity-compat-project: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + env: + DOTNET_CLI_TELEMETRY_OPTOUT: '1' + DOTNET_NOLOGO: '1' + steps: + - uses: actions/checkout@v6 + # Installed FIRST so the JDK 8 below is the one JAVA_HOME names. + - name: Set up JDK 17 for the application build + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + - run: echo "GRADLE_JAVA_HOME=$JAVA_HOME" >> $GITHUB_ENV + - name: Set up JDK 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'temurin' + cache: 'maven' + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + # xvfb for the CSS compiler, which opens an AWT frame. + - name: Install xvfb + run: bash scripts/ci/apt-get-install.sh xvfb + - name: Build Codename One Maven artifacts with the Unity profile + env: + RETRY_ONLY_MATCHING: transient + RETRY_ATTEMPTS: '5' + RETRY_DELAY_SECONDS: '60' + run: | + cd maven + bash ../scripts/ci/retry.sh xvfb-run -a mvn -B install -Plocal-dev-javase -Dunity-compat -DskipTests -Darchetype.test.skip=true -Dmaven.javadoc.skip=true + - name: Unit tests of the builder and the goals + working-directory: maven + run: xvfb-run -a mvn -B -Plocal-dev-javase -pl build-engine,codenameone-maven-plugin test -Dtest='Unity*Test,CompileUnityMojoTest' -Dsurefire.failIfNoSpecifiedTests=false + - name: Refresh archetype catalog + run: | + cd maven + xvfb-run -a mvn archetype:update-local-catalog -Plocal-dev-javase + xvfb-run -a mvn archetype:crawl -Plocal-dev-javase + - name: A Unity project imported and built as an application + working-directory: maven/integration-tests + env: + CN1_GRADLE_REQUIRED: '1' + run: xvfb-run -a bash unity-compat-project-test.sh + - name: Upload the build logs + if: failure() + uses: actions/upload-artifact@v7 + with: + name: unity-compat-project-logs + path: | + maven/integration-tests/build/unity-compat-project/*.log + maven/integration-tests/build/unity-compat-project/*.txt + maven/integration-tests/build/unity-compat-project/*.diff + maven/integration-tests/build/unity-compat-project/*.err + maven/integration-tests/build/unity-compat-project/app/common/target/unity/*.log + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index a6304d15f17..296b1c98e39 100644 --- a/.gitignore +++ b/.gitignore @@ -173,6 +173,8 @@ docs/developer-guide/_generated-build-hints.adoc # and the symptom is "cannot find symbol" on a class this branch just added. # Build with -Dmaven.repo.local=$(pwd)/.m2-repo instead. /.m2-repo/ +/.dotnet/ +/.nuget-packages/ # Generated by .github/scripts/generate-quality-report.py, uploaded as a CI # artifact rather than committed. diff --git a/CLAUDE.md b/CLAUDE.md index cdc8c665f87..8c0367b8779 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -115,7 +115,13 @@ build plugins share), `project-model` (the project-layout API), `android-res-compiler` and `android-compat` (the Android compatibility runtime), `codenameone-gradle-plugin` and `javac` (the in-tree Java compiler, `vm/JavaCompiler`), then enforces the result in -`.github/scripts/generate-quality-report.py`. +`.github/scripts/generate-quality-report.py`. `cil-translator` and +`unity-compat` (the Unity compatibility translator and runtime) are held to the +same script with SpotBugs, PMD and Checkstyle all three, but in +`.github/workflows/unity-compat.yml`: they build only with the .NET SDK, and +the Java 8 leg must not need one. That workflow's `paths` name both modules, +the core's `pmd.xml` and `checkstyle.xml` and the report script, so a change to +any of them cannot skip it. - **SpotBugs is a zero-findings gate.** *Any* finding of *any* pattern in *any* of those projects fails the build, and a project that produces no SpotBugs @@ -126,7 +132,8 @@ runtime), `codenameone-gradle-plugin` and `javac` (the in-tree Java compiler, `vm/ByteCodeTranslator/`, `maven/codenameone-maven-plugin/`, `maven/build-engine/`, `maven/project-model/`, `maven/android-res-compiler/`, `maven/android-compat/`, - `maven/codenameone-gradle-plugin/`, `maven/javac/`), scoped to the + `maven/codenameone-gradle-plugin/`, `maven/javac/`, + `maven/cil-translator/`, `maven/unity-compat/`), scoped to the class or method it applies to and with a comment explaining why. Keep the generated report at zero rather than tolerating known noise. - PMD and Checkstyle still gate on their own lists in the same script. @@ -141,6 +148,20 @@ mvn -B -DunitTests -DskipTests=true -pl core-unittests verify mvn -B -DskipTests=true -f ../vm/ByteCodeTranslator/pom.xml verify ``` +And the Unity compatibility modules, which need the .NET SDK. `verify` fails on +a SpotBugs finding or a Checkstyle error by itself; **PMD only writes +`target/pmd.xml`**, so the script is what judges it: + +```bash +cd maven && mvn -B -Dunity-compat -DskipTests=true -pl cil-translator,unity-compat clean verify +cd .. && QUALITY_REPORT_TARGET_DIRS=maven/cil-translator/target:maven/unity-compat/target \ + QUALITY_REPORT_REQUIRED_SPOTBUGS=cil-translator:unity-compat \ + QUALITY_REPORT_REQUIRED_PMD=cil-translator:unity-compat \ + QUALITY_REPORT_REQUIRED_CHECKSTYLE=cil-translator:unity-compat \ + python3 .github/scripts/generate-quality-report.py +scripts/check-cast-semantics.sh maven/unity-compat/target/classes +``` + **Run the `core-unittests` line too, and do not skip it because the first command "already covered" that module -- it does not.** `core-unittests` is declared inside the `unittests` profile, so `-am` never reaches it and its @@ -784,6 +805,182 @@ Traps that have already cost a fix: (`.github/workflows/android-compat.yml`); it fails if any shipped class still names `android/`. +### Unity compatibility: Unity 2D projects as Codename One apps + +A Unity project's `Assets` and `ProjectSettings` dropped into +`common/src/main/unity` (or imported with `cn1:import-unity-project`) builds +unmodified: + +- **`maven/cil-translator`** reads the assembly the C# compiler produced and + writes one class file per C# type (`com.codename1.cil`), and compiles every + `.unity` scene and `.prefab` to Java source, the generated + `com.codename1.generated.unity.UnityAppImpl` + (`com.codename1.unity.scenecompiler`). +- **`maven/unity-compat`** is the runtime the translated classes run against: + a .NET base library in Java, the `UnityEngine` value types (C#, translated at + build time) and the engine over `com.codename1.gaming`. Its `references` + classifier jar carries the three assemblies application scripts compile + against; `com.codename1.unitycompat.app.UnityApplication` is the lifecycle + class the imported main class extends. +- **The build step** (`build-engine/.../UnityProjectBuilder`, goal + `compile-unity` in `generate-sources`) runs `dotnet build` on a generated + project file, forks the translator and the scene compiler, and installs the + classes and images in the common module's classes. It has no compile + dependency on `cil-translator` -- the tool is resolved at the runtime's own + version and forked -- so `build-engine` builds without the profile below. +- **Gradle** runs the same builder as the `compileUnity` task + (`codenameone-gradle-plugin`, `AppSupport.registerUnity`), and adds the + runtime, the tool and the references itself for a project with + `src/main/unity`. The translated classes go to a directory of their own + (`build/cn1-unity/classes`, an output directory of the main source set and on + javac's classpath), never into `compileJava`'s destination: javac empties + that on a full recompile. The task reads the runtime jar from the + `compileClasspath` *configuration*; the source set's classpath contains the + task's own output, and reading that would make the task depend on itself. + +Both modules live behind the **`unity-compat` profile** (`-Dunity-compat`), +because building them needs the .NET SDK and the framework build must not. +An application needs the SDK only when it has Unity sources, and only for a +build that follows a change to them. + +Both are on the static analysis gates, at zero (see *Static Analysis Gates* for +where they run and how to reproduce them). Three things about the runtime's +exceptions, so that the next finding is fixed rather than added to them: + +- **C# member names are excused by package, nothing else is.** + `get_position`, `GetComponent` and `Item1` are what translated bytecode + calls, so SpotBugs' `NM_*` patterns are excluded for + `com.codename1.unitycompat.{system,unityengine}` and each class with such + members carries `@SuppressWarnings("PMD.MethodNamingConventions")`. A new + runtime class needs that annotation; a Java-named helper never does. +- **The translated classes are left out of SpotBugs by name** + (`UnityEngine.*`): they are generated bytecode in `target/classes` with no + Java source. PMD and Checkstyle read sources and never see them. A finding + there that points at wasted or wrong codegen is the translator's to fix. +- **Indexed loops stay indexed.** `// NOPMD ForLoopCanBeForeach` on a loop over + a list is deliberate: a for-each allocates an iterator, in code that runs + every frame, and several of those lists grow while they are walked. + +`scripts/check-cast-semantics.sh` covers `maven/unity-compat/target/classes` +(the runtime and the translated classes) whenever the module is built, and +`unity-compat.yml` names it explicitly. It is not one of the roots +`--require-all` demands, because PR CI's Java 8 leg does not build it. + +**A release publishes both**, in the core reactor pass and at the release's own +version, because the archetype and both build plugins resolve them at that +version. `release.yml` installs the SDK and passes `-Dunity-compat`; a release +build without the flag *succeeds* two modules short, so its "Confirm artifacts +on R2" step names both, and the `references` jar by file. `versions:set` reads +the modules of every profile, so `update-version.sh` bumps them unaided. + +Traps that have already cost a fix: + +- **A method returning a struct takes a hidden last parameter**, the object + the result is written into (`Names`, in `com.codename1.cil.translate`). Java + has no value types, `a + b` on two vectors is a call, and an object per call + measured an order of magnitude slower under ParparVM. A hand-written runtime + method that returns a value type follows the same convention, or the + translated call site passes one argument more than it declares; and the + reference it returns may be read but never kept. +- **Float arithmetic has to round the way C# rounds.** Natively that needs + `-ffp-contract=off`, or clang fuses a multiply and an add and the last bit + differs from the JVM; on JavaScript every `float` result goes through + `Math.fround`, because a JavaScript number is a double. Without both, a + seeded game diverges between targets after a few hundred frames, which is + what the committed traces compare. +- **A script with no namespace lands in package `global`**, since a class in + Java's default package cannot be imported by the generated factory. +- **No reflection.** Scenes, prefabs and serialized fields compile to code + that calls constructors and assigns fields; `Invoke("name")`, `Instantiate` and + `AddComponent` go through methods the translator writes into each script + (`$invoke`, `$copyFrom`, `$new`). Nothing looks a name up at run time, + because ParparVM strips what nothing references. +- **A C# `T[,]` is one flat Java array**, never an array of arrays: an + `MdArray*` holder (`com.codename1.unitycompat.system`) keeps the lengths, + and `a[i, j]` is a static `get`/`set` that checks each dimension by itself -- + an index past the end of one row is otherwise a valid element of the next. +- **A helper method that takes an enum has the enum in its name** + (`Split$StringSplitOptions`, `Names.enumSuffix`). An enum is an `int` once + erased, so `Split(char[], int)` and `Split(char[], StringSplitOptions)` + would be one Java method. +- **`Resources.Load` is resolved at build time.** The scene compiler embeds + what is under any `Resources` folder and generates `loadResource`, a chain + of string comparisons; a kind it cannot load is a build note, and the call + answers null. +- **`UnityEngine/*.class` is translated twice** -- into the runtime jar and + again with the scripts. The builder skips every class the runtime jar + already has; a second copy in the application would shadow the runtime's. +- **A Unity message is sent only if three places agree.** Unity calls + `OnMouseDown` by its name; here the translator writes a `$onMouseDown` + override for a method in `Translator.MESSAGES`, `MonoBehaviour` declares + the empty hook, and `UnityRuntime` calls it. A documented message in none of + them is in `UNSENT_MESSAGES`, and a script declaring one gets a build + warning with its file and line. Dispatching a new one means all three and + taking it off that list -- otherwise it compiles and is never called. +- **Code outside the frame reaches a script through + `UnityRuntime.callInFrame`** (`UnityApplication.callInFrame`), never + directly: a port may run the frame off the EDT. The queue is an + `AtomicReference` to an immutable array, as the key events are. +- **Animation, tiles and particles are compiled, not loaded.** The scene + compiler turns every `.anim`, `.controller`, tile asset, tilemap and + particle module into code (`AnimationAssets`, `TilemapAssets`, + `ParticleAssets`); nothing parses YAML on the device. A property a curve + names that the runtime cannot write is a build note, never a silent skip. +- **TextMeshPro and Cinemachine are our own classes** + (`com.codename1.unitycompat.{tmpro,cinemachine}`), matched to a scene by the + package's script GUID (`SceneCompiler.PACKAGE_CLASSES`) because a package's + scripts are not under `Assets`. The packages' sources are never read. A new + runtime package has to be added in three places or a gate fails far from + the cause: the source roots the `cil-translator` tests compile, and both + package patterns in `maven/unity-compat/spotbugs-exclude.xml`. +- **A tilemap collider is outlines, not boxes.** Solid cells are merged and + traced into chains, so a body sliding along a floor meets no seam between + two tiles; `platformer2d`'s trace holds the speed of such a slide. Box2D + makes no contact between two chains, so a tilemap never collides with + another tilemap -- `World.popContact` used to throw on that pair. +- **ParparVM copied an array of primitive arrays as primitives.** `int[][]` + has a primitive element class and two dimensions; `System.arraycopy` and + `clone()` on it skipped the reference path. Fixed in `cn1_globals.m` and + `nativeMethods.m`, held by `PrimitiveArrayOfArraysIntegrationTest`. +- **The world sweeps only while a body is Continuous.** Box2D finds times of + impact for every dynamic body against every static one by default; Unity + does it for a `Rigidbody2D` set to Continuous and no other. Box2D's switch is + per world, so `PhysicsWorld.sweepIfAnyAsks` turns it on while any body asks. + Left on, a level of Discrete bodies on a tilemap spent 70% of its step in + `World.solveTOI`. `platformer2d`'s pebble is the Continuous body: the frame + it turns back on (150, and 156 when nothing sweeps) is what holds this. +- **A level with a tilemap sorts dozens of broad-phase pairs a step.** + `Arrays.sort` takes a merge buffer above 31 elements, which was most of + what such a level allocated; `BroadPhase.sortPairs` sorts in place + (`BroadPhasePairSortTest`). Contact delivery likewise refills one + `Collision2D` per side of a pair in touch, as Unity's + `reuseCollisionCallbacks` does -- a script must not keep the one it is given. +- **Two images with one file name ship as `Name.png` and `Name~N.png`.** + Resources are flat on a device; a project with `Idle.png` in ten folders is + ordinary, so the name is made distinct rather than warned about. +- **`unity-compat-project-test.sh` resolves from `MAVEN_REPO_LOCAL`**, and + reads `~/.m2` when it is unset: export it to the checkout's `.m2-repo` after + installing `core`, both Unity modules and both build plugins there. +- **Clean room.** Unity's reference source and Unity's own assemblies are + never read, decompiled or shipped. The `UnityEngine.dll` in the references + jar is compiled from our own `src/main/csharp`, every body a bare `throw`; the + API comes from Unity's public documentation and from observed behaviour. +- **`maven/integration-tests/unity-compat-test.sh`** runs every sample under + `scripts/unity-compat-samples` on the JVM, natively and on JavaScript and + compares traces; **`unity-compat-project-test.sh`** imports one into an + archetype application, builds it with the Maven goal, converts it to + Gradle and holds every target's staged upload to the same entries from both + (`.github/workflows/unity-compat.yml`). +- **`netstandard.dll` in the references jar is Microsoft's**, from the NuGet + package `NETStandard.Library.Ref` (MIT). Its `LICENSE.TXT` and + `THIRD-PARTY-NOTICES.TXT` are kept verbatim in + `maven/unity-compat/third-party` and packed into the jar under + `META-INF/licenses`; the root `NOTICE` has the entry. The .NET SDK does not + bundle that package, so the first build of `maven/unity-compat` on a machine + restores it from nuget.org (later ones read `NUGET_PACKAGES`, offline). An + application build never restores anything: its generated project turns the + implicit framework reference off and names the three assemblies by path. + ### Integration Tests Located in `maven/integration-tests/`: diff --git a/CodenameOne/src/com/codename1/gaming/GameView.java b/CodenameOne/src/com/codename1/gaming/GameView.java index 000dfb9d42b..d6f4264d382 100644 --- a/CodenameOne/src/com/codename1/gaming/GameView.java +++ b/CodenameOne/src/com/codename1/gaming/GameView.java @@ -82,6 +82,9 @@ public abstract class GameView extends RenderView implements SpriteRenderer.Upda private ActionListener dragListener; private ActionListener releaseListener; private boolean formListenersAdded; + /// Set once the form has delivered a pointer event to this view directly, + /// with every finger in it. See `#addFormPointerListeners()`. + private boolean touchesArrive; public GameView() { super(new SpriteRenderer()); @@ -225,6 +228,14 @@ public double getInterpolationAlpha() { @Override protected void initComponent() { super.initComponent(); + // What the view hosts -- the GPU surface, or the placeholder that stands + // in for it -- is the view as far as a touch goes. A form hands a pointer + // event to the deepest component under it, which would be that child, and + // it does nothing with one; marked this way the form passes it on to its + // parent, and the multi-touch callbacks of this class see every finger. + for (int i = 0; i < getComponentCount(); i++) { + getComponentAt(i).setIgnorePointerEvents(true); + } addFormPointerListeners(); if (running) { setContinuous(true); @@ -239,12 +250,14 @@ protected void deinitialize() { super.deinitialize(); } - /// The GPU surface is hosted in a native peer that swallows the platform's - /// pointer events before they reach this component, so the usual - /// `#pointerPressed(int[], int[])` callbacks never fire over the surface. Instead - /// we listen at the form level (those listeners fire for every pointer event, - /// regardless of which component is hit) and route the touches to the on-screen - /// controls ourselves. + /// Form-level listeners fire for every pointer event, whichever component is + /// hit, but they carry the first finger only. They are what feeds the view + /// until `#pointerPressed(int[], int[])` and its siblings have been seen to + /// arrive -- those carry every finger, and once they do, a press or a drag + /// heard here as well would take the on-screen controls back to one finger + /// between two reports of all of them. A release is always routed: it means + /// the last finger lifted, saying so twice changes nothing, and it must not be + /// lost when the form delivers it to another component. private void addFormPointerListeners() { if (formListenersAdded) { return; @@ -257,13 +270,17 @@ private void addFormPointerListeners() { pressListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - routeFormTouch(e.getX(), e.getY(), true, true, false); + if (!touchesArrive) { + routeFormTouch(e.getX(), e.getY(), true, true, false); + } } }; dragListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - routeFormTouch(e.getX(), e.getY(), true, false, false); + if (!touchesArrive) { + routeFormTouch(e.getX(), e.getY(), true, false, false); + } } }; releaseListener = new ActionListener() { @@ -379,18 +396,34 @@ public void keyReleased(int keyCode) { input.keyUp(keyCode); } + /// A key that is held is held, and nothing more. The display reports a key + /// that stays down as repeating -- 800 ms after the press and then every few + /// milliseconds, by the wall clock -- and a component turns each repeat into a + /// press and a release unless it says otherwise. For a text field that is a + /// character typed again; here it would let go of a key the player still holds: + /// `GameInput#isKeyDown(int)` went false one repeat interval into every held + /// direction on a port that does not re-send the press itself, and a subclass + /// that forwards `#keyReleased(int)` saw the key come up. + @Override + public void keyRepeated(int keyCode) { + // Deliberately empty: see above. + } + @Override public void pointerPressed(int[] x, int[] y) { + touchesArrive = true; routeTouches(x, y, true, true, false); } @Override public void pointerDragged(int[] x, int[] y) { + touchesArrive = true; routeTouches(x, y, true, false, false); } @Override public void pointerReleased(int[] x, int[] y) { + touchesArrive = true; routeTouches(x, y, false, false, true); } diff --git a/CodenameOne/src/com/codename1/gaming/physics/box2d/collision/broadphase/BroadPhase.java b/CodenameOne/src/com/codename1/gaming/physics/box2d/collision/broadphase/BroadPhase.java index a9375a590b6..dada8b5b3ec 100644 --- a/CodenameOne/src/com/codename1/gaming/physics/box2d/collision/broadphase/BroadPhase.java +++ b/CodenameOne/src/com/codename1/gaming/physics/box2d/collision/broadphase/BroadPhase.java @@ -23,8 +23,6 @@ ******************************************************************************/ package com.codename1.gaming.physics.box2d.collision.broadphase; -import java.util.Arrays; - import com.codename1.gaming.physics.box2d.callbacks.DebugDraw; import com.codename1.gaming.physics.box2d.callbacks.PairCallback; import com.codename1.gaming.physics.box2d.callbacks.TreeCallback; @@ -169,7 +167,7 @@ public final void updatePairs(PairCallback callback) { m_moveCount = 0; // Sort the pair buffer to expose duplicates. - Arrays.sort(m_pairBuffer, 0, m_pairCount); + sortPairs(m_pairBuffer, m_pairCount); // Send the pairs back to the client. int i = 0; @@ -197,6 +195,28 @@ public final void updatePairs(PairCallback callback) { // m_tree.rebalance(Settings.TREE_REBALANCE_STEPS); } + /// Sorts the first `count` pairs by their proxy ids, in place. `Arrays.sort` takes a merge + /// buffer for anything over a few dozen elements, and this runs on every step of a world + /// with many moving proxies. A shell sort needs none, and which of two equal pairs comes + /// first is of no consequence: the caller skips duplicates. + static void sortPairs(Pair[] pairs, int count) { + int gap = 1; + while (gap < count / 3) { + gap = gap * 3 + 1; + } + for (; gap >= 1; gap /= 3) { + for (int i = gap; i < count; i++) { + Pair moved = pairs[i]; + int j = i; + while (j >= gap && pairs[j - gap].compareTo(moved) > 0) { + pairs[j] = pairs[j - gap]; + j -= gap; + } + pairs[j] = moved; + } + } + } + /// Query an AABB for overlapping proxies. The callback class is called for each proxy that /// overlaps the supplied AABB. /// diff --git a/CodenameOne/src/com/codename1/gaming/physics/box2d/dynamics/World.java b/CodenameOne/src/com/codename1/gaming/physics/box2d/dynamics/World.java index 2e2daa1cba9..20fb6521db0 100644 --- a/CodenameOne/src/com/codename1/gaming/physics/box2d/dynamics/World.java +++ b/CodenameOne/src/com/codename1/gaming/physics/box2d/dynamics/World.java @@ -211,6 +211,14 @@ public Contact popContact(Fixture fixtureA, int indexA, Fixture fixtureB, int in final ShapeType type2 = fixtureB.getType(); final ContactRegister reg = contactStacks[type1.ordinal()][type2.ordinal()]; + // Codename One: two shapes with no collision routine between them -- a chain + // against a chain, an edge against an edge or a chain -- have no register at + // all, and Box2D's answer for such a pair is no contact. Reading the creator + // of a null register threw here the moment a dynamic body made of chains + // came near another chain. + if (reg == null) { + return null; + } final IDynamicStack creator = reg.creator; if (creator != null) { if (reg.primary) { diff --git a/NOTICE b/NOTICE index dc50ab27df2..63816df6176 100644 --- a/NOTICE +++ b/NOTICE @@ -129,3 +129,46 @@ ASM is distributed under the BSD 3-Clause license: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +================================================================================ +.NET Standard reference assembly +================================================================================ + +The "references" artifact of maven/unity-compat (codenameone-unity-compat, +classifier references) holds the assemblies the C# scripts of a Unity project +are compiled against. One of them, netstandard.dll, is the .NET Standard 2.1 +reference assembly from the NuGet package NETStandard.Library.Ref 2.1.0 +(https://www.nuget.org/packages/NETStandard.Library.Ref/2.1.0), redistributed +unmodified. It declares the API of the .NET base library and contains no +implementation; it is read by the C# compiler and the translator at build time +and is never part of an application. + +The package's LICENSE.TXT and THIRD-PARTY-NOTICES.TXT are kept verbatim in +maven/unity-compat/third-party/NETStandard.Library.Ref and ship inside the +artifact under META-INF/licenses/NETStandard.Library.Ref. + +NETStandard.Library.Ref is distributed under the MIT license: + + The MIT License (MIT) + + Copyright (c) .NET Foundation and Contributors + + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/docs/demos/common/src/main/snippets/developer-guide/unity-interop.cs b/docs/demos/common/src/main/snippets/developer-guide/unity-interop.cs new file mode 100644 index 00000000000..3a376e8ab78 --- /dev/null +++ b/docs/demos/common/src/main/snippets/developer-guide/unity-interop.cs @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +// Developer guide snippets for the Unity compatibility chapter. + +// tag::unity-interop-csharp-001[] +using System; +using System.Collections.Generic; +using UnityEngine; + +public class Board : MonoBehaviour +{ + public GameObject tilePrefab; + + private int[,] cells; + private readonly Dictionary tiles = new Dictionary(); + + void Start() + { + TextAsset level = Resources.Load("level1"); + string[] rows = level.text.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + cells = new int[rows.Length, rows[0].Split(',').Length]; + for (int r = 0; r < cells.GetLength(0); r++) + { + string[] columns = rows[r].Split(','); + for (int c = 0; c < cells.GetLength(1); c++) + { + int.TryParse(columns[c], out cells[r, c]); + if (cells[r, c] != 0) + { + GameObject tile = Instantiate(tilePrefab, new Vector3(c, -r, 0), Quaternion.identity); + tiles[tile] = new Vector2(c, r); + } + } + } + PlayerPrefs.SetInt("lastLevel", 1); + } +} +// end::unity-interop-csharp-001[] + +// tag::unity-interop-csharp-002[] +// What the game asks of the application around it. The game compiles +// against this interface alone; the application supplies the object. +public interface IPlatformServices +{ + void SignIn(); + void Share(string text); + void ShowLeaderboard(int score); +} + +public static class Platform +{ + public static IPlatformServices Services; +} +// end::unity-interop-csharp-002[] + +// tag::unity-interop-csharp-003[] +public class Score : MonoBehaviour +{ + public int points; + + public int Best { get; private set; } + + public void Add(int amount) + { + points += amount; + if (points > Best) + { + Best = points; + } + } + + public Vector2 Where() + { + return transform.position; + } + + private void Start() + { + Platform.Services?.SignIn(); + } + + private void OnApplicationPause(bool paused) + { + Debug.Log("score paused=" + paused); + } + + private void Update() + { + if (Platform.Services == null) + { + return; + } + if (Input.GetKeyDown(KeyCode.L)) + { + Platform.Services.ShowLeaderboard(Best); + } + if (Input.GetKeyDown(KeyCode.S)) + { + Platform.Services.Share("I scored " + Best); + } + } +} +// end::unity-interop-csharp-003[] diff --git a/docs/demos/common/src/main/snippets/developer-guide/unity-interop.sh b/docs/demos/common/src/main/snippets/developer-guide/unity-interop.sh new file mode 100644 index 00000000000..5ea9ffb8a68 --- /dev/null +++ b/docs/demos/common/src/main/snippets/developer-guide/unity-interop.sh @@ -0,0 +1,40 @@ +// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline. + +// tag::unity-interop-bash-001[] +mvn cn1:import-unity-project -Dsource=/path/to/MyUnityGame +// end::unity-interop-bash-001[] + +// tag::unity-interop-bash-002[] +mvn cn1:run +mvn cn1:build -Dcodename1.platform=ios +// end::unity-interop-bash-002[] + +// tag::unity-interop-bash-003[] +dotnet --list-sdks +mvn package -Dcn1.unity.dotnet=/opt/dotnet/dotnet +// end::unity-interop-bash-003[] + +// tag::unity-interop-bash-004[] +./gradlew run +./gradlew buildIos -Pcn1.unity.dotnet=/opt/dotnet/dotnet +// end::unity-interop-bash-004[] + +// tag::unity-interop-bash-005[] +mvn package +// end::unity-interop-bash-005[] + +// tag::unity-interop-bash-006[] +mvn cn1:build -Dcodename1.platform=android +mvn cn1:build -Dcodename1.platform=ios +mvn cn1:build -Dcodename1.platform=javascript +// end::unity-interop-bash-006[] + +// tag::unity-interop-bash-007[] +mvn package -Dcn1.unity.skip=true +// end::unity-interop-bash-007[] + +// tag::unity-interop-bash-008[] +less common/target/unity/dotnet.log +less common/target/unity/translate.log +less common/target/unity/scene-compiler.log +// end::unity-interop-bash-008[] diff --git a/docs/demos/common/src/main/snippets/developer-guide/unity-interop.txt b/docs/demos/common/src/main/snippets/developer-guide/unity-interop.txt new file mode 100644 index 00000000000..aa8dd7eb236 --- /dev/null +++ b/docs/demos/common/src/main/snippets/developer-guide/unity-interop.txt @@ -0,0 +1,128 @@ +// tag::unityInteropMainClass[] +package com.example.mygame; + +public class MyGame extends com.codename1.unitycompat.app.UnityApplication { + @Override + protected void installProject() { + com.codename1.generated.unity.UnityAppImpl.install(); + } +} +// end::unityInteropMainClass[] + +// tag::unityInteropApplication[] +public class MyGame extends UnityApplication { + private final GameServices services = new GameServices(this); + private final Label scoreLabel = new Label("Score 0"); + + @Override + protected void installProject() { + UnityAppImpl.install(); + } + + @Override + protected void onProjectInstalled() { + // No scene is built yet: every Awake and Start finds this set. + Platform.Services = services; + } + + @Override + protected Form createForm(UnityGameView gameView) { + Form form = new Form(new BorderLayout()); + Toolbar toolbar = new Toolbar(); + form.setToolbar(toolbar); + form.setTitle("My Game"); + form.setScrollable(false); + toolbar.addMaterialCommandToRightBar("", FontImage.MATERIAL_LEADERBOARD, + e -> callInFrame(() -> services.ShowLeaderboard(score().get_Best()))); + toolbar.addMaterialCommandToSideMenu("Restart", FontImage.MATERIAL_REPLAY, + e -> callInFrame(() -> score().points = 0)); + form.add(BorderLayout.CENTER, gameView); + form.add(BorderLayout.SOUTH, scoreLabel); + return form; + } + + @Override + protected void onStarted(Form gameForm, UnityGameView gameView) { + // Twice a second: read the score inside the frame, show it on the + // event dispatch thread. + UITimer.timer(500, true, gameForm, () -> callInFrame(() -> { + int points = score().points; + CN.callSerially(() -> { + scoreLabel.setText("Score " + points); + gameForm.revalidate(); + }); + })); + } + + // Only ever called inside the frame. + private static Score score() { + return (Score) GameObject.Find("Ball").GetComponent(Score.class); + } +} +// end::unityInteropApplication[] + +// tag::unityInteropServices[] +final class GameServices implements IPlatformServices { + private final UnityApplication app; + + GameServices(UnityApplication app) { + this.app = app; + } + + // Scripts call these inside a frame, which isn't always on the event + // dispatch thread. Each one hands its work to that thread. + + @Override + public void SignIn() { + CN.callSerially(() -> { + if (Preferences.get("player", null) != null) { + return; + } + GoogleConnect.getInstance().signIn( + "YOUR_CLIENT_ID.apps.googleusercontent.com", + "com.example.mygame:/oauth2redirect", + "openid", "email" + ).ready(tokens -> Preferences.set("player", tokens.getEmail())); + }); + } + + @Override + public void Share(String text) { + CN.callSerially(() -> CN.share(text, null, null)); + } + + @Override + public void ShowLeaderboard(int score) { + CN.callSerially(() -> showLeaderboard(score)); + } + + private void showLeaderboard(int score) { + app.getView().pause(); + Form board = new Form(BoxLayout.y()); + Toolbar toolbar = new Toolbar(); + board.setToolbar(toolbar); + board.setTitle("Leaderboard"); + board.add(new Label("Your best: " + score)); + toolbar.setBackCommand("", e -> { + app.getForm().showBack(); + app.getView().resume(); + }); + board.show(); + } +} +// end::unityInteropServices[] + + // tag::unityInteropCallingRules[] + // Inside a frame: in a Runnable given to callInFrame, or in a + // method a script called. + // By the name of its object, then by the script's class. + Score score = (Score) GameObject.Find("Ball").GetComponent(Score.class); + // Or the first one in the scene, whatever object it is on. + Score first = (Score) com.codename1.unitycompat.unityengine.Object + .FindObjectOfType(Score.class); + + int points = score.points; // public int points; + int best = score.get_Best(); // public int Best { get; } + Vector2 where = score.Where(new Vector2()); // public Vector2 Where() + float height = where.y; + // end::unityInteropCallingRules[] diff --git a/docs/developer-guide/Unity-Interop.asciidoc b/docs/developer-guide/Unity-Interop.asciidoc new file mode 100644 index 00000000000..9855a0f58d8 --- /dev/null +++ b/docs/developer-guide/Unity-Interop.asciidoc @@ -0,0 +1,1194 @@ +== Running Unity 2D games + +A Unity 2D project -- C# scripts, scenes, prefabs, sprites and project +settings -- builds and runs as a Codename One application without changes to +its scripts or its assets. You copy the project into a Codename One project, +the build compiles the C# and translates it to Java bytecode, and the game +runs on every Codename One target: iOS, Android, the JavaScript port, the +desktop ports and the simulator. + +image::img/unity-interop-fruitopia.png["Fruitopia, an open source Unity platformer, running on the Codename One runtime: the frog hero in mid-jump beside a spiked ball, over a tile map level with a fruit counter",scaledwidth=70%] + +No Unity player and no .NET runtime ship with the application. Scripts become +ordinary classes, scenes and prefabs become generated code that creates their +objects, and a runtime that implements the `UnityEngine` API draws the game +into a Codename One component. + +That component is the point. The game sits on a Codename One form, inside a +Codename One application, so the same project can put a toolbar and a side +menu around it, show a settings screen or a store, and use sign-in, in-app +purchase, push, advertising and sharing from one Java codebase on every +target. Scripts and Java call each other; +<> shows how. + +The scope is 2D games built from sprites, tile maps, 2D physics, animation +controllers, particle effects, a canvas interface and scripts. Five open +source games run unmodified today, among them the platformer in the image +above; see <>. Read +<> before you plan a port. + +=== Requirements + +Besides what every Codename One project needs, a project with Unity sources +needs the https://dotnet.microsoft.com/download[.NET SDK], version 6 or newer, +on the machine that runs the build. The build uses its C# compiler and nothing +else: no package is downloaded and no Unity installation or Unity license is +needed. + +The build looks for the `dotnet` command in this order: + +. The `cn1.unity.dotnet` property, which names the command or its directory. +. The `DOTNET_ROOT` environment variable. +. The `PATH`. + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-003,indent=0] +---- + +A project with no Unity sources doesn't need the SDK. An unchanged Unity +project isn't compiled again, so only the builds that follow a change to a +script or an asset use it. + +The Unity project must store its assets as text, which is the default in +current Unity versions (*Edit > Project Settings > Editor > Asset +Serialization > Force Text*). The +build reads scenes, prefabs and `.meta` files as the YAML text Unity writes in +that mode. + +=== Quick start + +Create a Codename One application project (for example from +https://start.codenameone.com[the initializr]), then import the Unity project +into it: + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-001,indent=0] +---- + +Then run and build as usual: + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-002,indent=0] +---- + +The next section walks through the same steps and what each one prints. + +[[unity-interop-porting]] +=== Porting a project, step by step + +==== 1. Import the project + +Run `cn1:import-unity-project` from the root of the Codename One project and +point `-Dsource` at the directory that holds `Assets` and `ProjectSettings`. +The goal does four things: + +* Copies `Assets` and `ProjectSettings` into `common/src/main/unity`. It + leaves out what the Unity editor derives from them (`Library`, `Temp`, + `obj`) and the version control directories. +* Adds the `codenameone-unity-compat` runtime and the `compile-unity` goal to + the `common` module's `pom.xml`. +* Rewrites the Codename One main class so that it starts the game. The + previous class is kept beside it with a `.pre-unity-import` suffix. +* Prints what it copied, and lists every file the build won't use. A + compiled plugin shows up here as `not built: Assets/Plugins/Foo.dll + (compiled code: only C# source is translated)`. + +The copy mirrors the Unity project. Keep editing the game in Unity and run +the import again: it replaces the files it copied, removes the ones the Unity +project no longer has, and leaves alone both the files you added by hand and +a main class you changed since the last import. + +You can also copy the files by hand. The directory mirrors the root of a Unity +project: + +---- +common/src/main/unity/ + Assets/ scripts, scenes, prefabs, sprites and their .meta files + ProjectSettings/ the input axes, tags, layers and physics settings +---- + +Keep the `.meta` files. Unity identifies an asset by the identifier in its +`.meta` file, and that's how a scene finds the sprite or the script it refers +to. + +==== 2. Run the first build + +Build the project. This is the step that needs the .NET SDK: + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-005,indent=0] +---- + +The build compiles the scripts, translates them and compiles the scenes. A +project inside the supported surface ends with the line `Compiled the Unity +project`. A script that uses an API outside the surface stops the build here +with the script file, the line and the name of the missing member, so the list of +changes a port needs is in front of you after the first build rather than +after the first crash. <> shows +both forms of that message. + +==== 3. Read the build report + +The translator and the scene compiler report everything they left out or +approximated. Maven and Gradle print each line in the build output, prefixed +`src/main/unity:`: + +* A *warning* names something that changes how the game behaves or looks: a + component the runtime doesn't have, a perspective camera made orthographic, + a sprite a scene refers to that isn't under `Assets`, a script method Unity + would call and the runtime doesn't. The build log shows + these at warning level, and the last line counts them: `Compiled the Unity + project with 2 warning(s): what they name will not behave as it does in + Unity`. +* A *note* names something left out that doesn't change the game, or a + substitution you might want to know about. Maven shows notes at info level. + +A line about a scene names the scene or prefab file, the GameObject and the +component's file ID, the number after the `&` in the scene's YAML. A warning +about a script names the class, the method, the file and the line. This is the +complete report of the Sokoban game from +<>, seven notes and no warnings, shown +without the prefix: + +---- +Sokoban.unity: the FlareLayer component of GameObject 'Camera with script' (component file ID 1187623029) was left out; nothing in a 2D scene is drawn through it +Sokoban.unity: the GUILayer component of GameObject 'Camera with script' (component file ID 1187623030) was left out; nothing in a 2D scene is drawn through it +Sokoban.unity: the StandaloneInputModule of GameObject 'EventSystem' (component file ID 1460168504) was left out; the event system beside it routes the pointer to the controls itself, and nothing moves between controls by keyboard +Hero.png is a polygon sprite: it was cut to its outline when the project was built, into unity-polygon-Hero.png (256 by 256 pixels), and is drawn as that image +Ball.png is a polygon sprite: it was cut to its outline when the project was built, into unity-polygon-Ball.png (256 by 256 pixels), and is drawn as that image +Sokoban.unity: the image of GameObject 'RestartButton' (file ID 1532672074) uses Unity's built-in interface sprite 10905, which is not in the project; a plain rectangle of the image's colour is drawn instead +the input settings define joystick axes; those read as zero, and the keys of the same axes work +---- + +The full output of each tool is in `common/target/unity`: + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-008,indent=0] +---- + +==== 4. Run in the simulator + +`mvn cn1:run` opens the game in the Codename One simulator. It shows the +first scene of the Unity build settings in a form that fills the screen. If +the build settings list no scene, every scene under `Assets` is compiled in +order of path and the first one starts. + +The simulator has a keyboard, so a game written for the desktop plays as it +is. Keys reach `Input` under Unity's key codes, the arrow keys drive the +default `Horizontal` and `Vertical` axes beside WASD, and the pointer is the +mouse. + +==== 5. Plan the input for a phone + +Touches reach a script two ways. `Input.touchCount`, `Input.touches` and +`Input.GetTouch` report every finger on the screen with its phase, so a game +written for touch plays as it is. The first finger also reads as mouse +button 0, with `Input.mousePosition` at the touch, and canvas buttons respond +to it. A game played with the mouse or with canvas buttons therefore needs +nothing more on a phone. + +A game played with keys needs a way to press them on a device that has none. +The game's view accepts key events from Java code: call `keyPressed` and +`keyReleased` on it with the character of the key, and the script sees +`Input.GetKey` exactly as if a keyboard had sent it. Add Codename One buttons +around the game for that, as +<> +describes. + +==== 6. Build for a device + +Device builds are ordinary Codename One builds. Nothing about them is +specific to Unity sources: + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-006,indent=0] +---- + +==== 7. Iterate + +After a change to a script, a scene or an asset, build again. The build +fingerprints `Assets` and `ProjectSettings` and repeats the C# compile, the +translation and the scene compile only when one of those files changed. A +change to your Java code alone doesn't run the .NET SDK. + +To leave the Unity project out of one build, set `cn1.unity.skip`. The classes +an earlier build produced stay in place: + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-007,indent=0] +---- + +[[unity-interop-codename-one]] +=== Using Codename One from your game + +The game isn't the whole application. It's one component, a `UnityGameView`, +on a Codename One form, in an application whose main class is Java. The rest +of this guide therefore applies to the same project and reaches every target +the game does: the user interface toolkit and its themes, sign-in, in-app +purchase, push, advertising, sharing, storage, and networking. + +Code travels in three directions, and this section takes them in turn: + +* Codename One around the game: forms, a toolbar, a side menu and other + screens, written in Java. +* Java calling the game: reading a score, or restarting a level. +* The game calling Codename One: a script that asks to sign in, to share a + result or to open a leaderboard. + +The samples are one small application. Its Unity project has two scripts +besides the game's own, and its Java side is two classes. + +[[unity-interop-entry-point]] +==== The entry point + +The import writes a main class that extends +`com.codename1.unitycompat.app.UnityApplication`. A project that has no main +class of its own gets the same class generated by the build. This is the whole +class, for an application whose main class is `com.example.mygame.MyGame`: + +[source] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.txt[tag=unityInteropMainClass,indent=0] +---- + +`installProject` is the one method you must have. It names the class the +scene compiler generated, in source, because a device build has no reflection +to find it by name. `UnityApplication` gives you more places to work from, in +the order they're called: + +* `onProjectInstalled()` runs once the project is installed and before the + first scene is built, so before the first `Awake` of any script. +* `createForm(UnityGameView gameView)` returns the form the game is shown in. +* `onStarted(Form gameForm, UnityGameView gameView)` runs once, after the + first scene is loaded and the game loop runs. +* `getForm()` and `getView()` return the same two objects later. +* `callInFrame(Runnable)` is static, and runs your code inside the game's + frame. <> explains + when you need it. + +==== Codename One screens around the game + +Override `createForm` to decide what surrounds the game. The default returns a +form with a `BorderLayout`, a hidden title area and the view in the center. +Return your own form instead; the one rule is that it must contain the view. +This main class gives the game a title bar with a leaderboard command, a side +menu with a Restart command and a score label under the playfield: + +[source] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.txt[tag=unityInteropApplication,indent=0] +---- + +`UnityGameView` is in `com.codename1.unitycompat.unityengine.ui`. It extends +the `GameView` of the `com.codename1.gaming` package, so it sizes and lays out +like any other component. Here it takes the center of a `BorderLayout`, and +the game draws into whatever size the layout gives the view. + +Everything around the view is ordinary Codename One user interface. The +toolbar, the side menu and the label take their look from the application's +theme, so you style them in `theme.css` as +<> describes, and +<<_toolbar,Toolbar>> covers the commands, the search field and the side menu. +The theme doesn't reach inside the view: what the game draws is the game's +own. + +==== Another form, and back to the game + +A settings screen, a store or a leaderboard is a second form. Pause the view +before you show it and resume the view when the player returns. A paused view +runs no script and advances no game time; it keeps its last frame. Scripts +that declare `OnApplicationPause` or `OnApplicationFocus` are told of both: + +[source] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.txt[tag=unityInteropServices,indent=0] +---- + +`showLeaderboard` at the end of the class is the part that matters here: it +pauses the view, shows a form with a back command, and the back command shows +the game's form again and resumes. + +`UnityApplication` already pauses the game when the application goes to the +background and resumes it on return, so no frame is simulated while nobody +sees it and `Time.time` doesn't jump. A script that calls `Application.Quit()` +exits the application. + +==== On-screen controls + +For a game that reads keys, add buttons in `createForm` and forward their +press and release to the view. `gameView.keyPressed('a')` followed by +`gameView.keyReleased('a')` is one tap of the A key, and the script's +`Input.GetKey(KeyCode.A)` is true from one call to the other. Letters, digits and the space +character are passed as themselves. For the arrow keys pass the key code of +the matching game action, which `Display.getInstance().getKeyCode(Display.GAME_LEFT)` +returns. The view queues these calls and hands them to the game at the start +of its next frame, so a button's listener makes them directly. + +[[unity-interop-java-to-game]] +==== Calling the game from Java + +A translated script is an ordinary class, and Java code in the same module +calls it by name. A script with no namespace is in the package `global`; a +script in a namespace is in the package of that name. This is the script the +samples read: + +[source,csharp] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.cs[tag=unity-interop-csharp-003,indent=0] +---- + +And this is Java finding it and reading it, in code that runs inside a frame: + +[source] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.txt[tag=unityInteropCallingRules,indent=0] +---- + +The rules a Java author needs: + +* *Finding an object.* `GameObject.Find` takes the object's name and returns + `null` when the scene has none. `GetComponent` and `FindObjectOfType` take + the script's class where C# takes a type argument, and return an `Object` + to cast. The engine's classes are in + `com.codename1.unitycompat.unityengine`; its own `Object` class shares a + name with `java.lang.Object`, so write that one in full. +* *When.* A scene's objects exist once the scene is loaded, so from + `onStarted` on. A scene load replaces them, apart from the ones a script + kept with `DontDestroyOnLoad`, so find an object again after one. +* *Fields and methods* keep their C# names, capitals included. +* *Properties* are a pair of methods: `Best` is `get_Best()` and + `set_Best(int)`. +* *Types.* `int`, `float` and `bool` are `int`, `float` and `boolean`. A + `string` is a `java.lang.String`. An enumeration is an `int`. A C# array of + those is the Java array. A `List` is a + `com.codename1.unitycompat.system.collections.generic.List_1`, without its + type argument. +* *Value types.* `Vector2`, `Vector3`, `Color` and the other `UnityEngine` + value types are classes of the Java package `UnityEngine`, with their fields + public. A method that returns one takes an extra last argument, the object + the result is written into: `Where()` in C# is `Where(new Vector2())` in + Java. +* *Access.* The translated members are all public. A member the script + declared private isn't part of what the script offers; leave it alone. + +One rule covers threads. Scripts run inside the game's frame, on the thread +that draws it, and on some ports that isn't the event dispatch thread your +listeners run on. So: + +* Touch game objects and scripts inside `callInFrame`. +* Touch Codename One components on the event dispatch thread. + +`callInFrame(Runnable)` runs the code at the start of the next frame, before +any script. It's a static method of `UnityApplication`, and the view has the +same method. Calls run in the order they were made, and they run while the +view is paused too. The main class above uses it three times. The Restart +command sets a field of the script. The leaderboard command reads the best +score and hands it on. The timer reads the points inside the frame and then +passes the number to `CN.callSerially`, which sets the label on the event +dispatch thread. + +Code that a script calls is already inside the frame, and may use any script +directly. + +[[unity-interop-game-to-java]] +==== Calling Codename One from the game + +A script compiles against the `UnityEngine` API and the .NET base library, so +it can't name a Java class. It can name an interface of its own. Declare, in +the Unity project, what the game needs from the application, and a static +field to hold the object that provides it: + +[source,csharp] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.cs[tag=unity-interop-csharp-002,indent=0] +---- + +The interface is translated with the scripts and becomes the Java interface +`global.IPlatformServices`. Implement it in Java, with the whole Codename One +API at hand. `GameServices`, shown earlier, is that implementation: `SignIn` +uses `GoogleConnect`, `Share` opens the platform's share sheet through +`CN.share` and `ShowLeaderboard` shows a form. + +Three things make the bridge work: + +* *Register it before the first scene loads.* The main class assigns + `Platform.Services` in `onProjectInstalled`. Every `Awake` and `Start` then + finds it set; `Score` calls `SignIn()` from its `Start`. +* *Hand the work to the event dispatch thread.* A script calls the interface + from inside a frame. Each method wraps what it does in `CN.callSerially`, + and returns at once so the frame isn't held up. +* *Keep the interface to simple types.* `int`, `float`, `bool` and `string` + cross unchanged. A method that returns a value answers on the game's + thread: return something the application already holds, and don't touch a + component there. + +The script side stays plain C#. `Platform.Services?.Share("I scored " + Best)` +does nothing when no object is registered, so the scripts don't depend on the +application being there. + +From the Java side of that interface the whole platform is in reach: + +* *Sign-in* with Google, Apple, Facebook, Microsoft or any OpenID Connect + provider: <>. +* *In-app purchase and subscriptions* through `Purchase`: + <<_in_app_purchase,In-app purchase>>. +* *Advertising*, including rewarded and interstitial formats between levels: + <<_advertising,Advertising>>. +* *Push notifications*: <<_push_notifications,Push notifications>>. +* *Sharing* a score or a screenshot: `CN.share`, or the + <> component. +* *Storage and networking* for saved games and online scores: `Preferences`, + <<_storage,Storage>> and + <<_network_manager_connection_request,`ConnectionRequest`>>. +* *Anything a platform SDK offers* that Codename One doesn't wrap: + <<_native_interfaces,native interfaces>>. + +`PlayerPrefs` needs no bridge. The runtime keeps it in Codename One's +`Preferences` storage, so a high score outlives the application on every +target. + +=== How the build works + +A single step runs as part of the normal build of the `common` module: +*`compile-unity`*, in `generate-sources`. It does three things: + +. The .NET SDK compiles every `.cs` file under `Assets` to one assembly, + with the language level set to C# 9. It leaves out the scripts in an `Editor` directory, as Unity leaves them + out of a player build. The scripts compile against a `UnityEngine` assembly + that ships with the runtime, so a script that uses a `UnityEngine` member + the runtime doesn't have fails here, with the C# compiler's own file and + line. +. The translator reads the assembly and writes Java class files, one class + for each C# type. A type in a namespace lands in the package of the same + name. A type with no namespace, which is what most Unity scripts are, lands + in the package `global`, because a Java class in the default package can't + be imported. The translator also checks every call into the .NET base + library against the runtime and lists the members the runtime lacks. +. The scene compiler turns every scene and prefab into Java source: a + generated class, `com.codename1.generated.unity.UnityAppImpl`, that creates + the objects with `new`, assigns the values the Inspector stored and connects + the references. It copies the sprites, sounds and text assets next to it. + +Nothing is created by reflection and no scene file is parsed on the device. +That's what lets the same code run on iOS and in the browser, where the +translated application keeps only the code something refers to. Three things +Unity resolves by name at run time are resolved by the build instead: + +* `Invoke("Fire", 1f)` and `InvokeRepeating` call a dispatcher the translator + writes into each script, which maps the method names of that script to + calls. +* A button's persistent `onClick` calls, the ones set up in the Inspector, + become direct calls in the generated scene code. +* `Resources.Load("level1")` is a lookup the scene compiler writes from the + files it found under the `Resources` folders. The asset is already part of + the application when a script asks for it. + +Two representations are worth knowing as a script author: + +* *Value types keep C#'s copy semantics.* `Vector2`, `Vector3`, `Quaternion`, + `Color`, `Bounds` and your own structs are copied on assignment and when + passed, exactly as C# specifies. Arithmetic on them writes into storage the + caller owns instead of allocating an object for each result, so vector math + in `Update` doesn't produce garbage. +* *A rectangular array is one flat array.* `int[,]` and arrays of higher rank + are stored as a single array with every index checked against its own + dimension. A tile grid costs one allocation and each access is one multiply + and one add. + +The `codenameone-unity-compat` dependency is added to the `common` module by +the project's `unity-compat` profile, which activates when `src/main/unity` +exists. + +=== Gradle projects + +A Gradle project builds a Unity project the same way. The plugin sees +`src/main/unity`, adds the `codenameone-unity-compat` runtime to the +application and runs a `compileUnity` task before the Java sources compile, so +there is no dependency to declare and no task to call. A Maven project that +has a Unity project keeps it when you convert it with `cn1:convert-to-gradle`. + +The import is a Maven goal. In a project that was created for Gradle, copy +`Assets` and `ProjectSettings` into `src/main/unity` by hand. Then either +delete the main class the project came with, and the build generates one that +starts the game, or change it as <> +describes. + +Name the .NET SDK with `-Pcn1.unity.dotnet` when it isn't on the `PATH`: + +[source,bash] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.sh[tag=unity-interop-bash-004,indent=0] +---- + +Gradle skips `compileUnity` as up to date while the Unity project is +unchanged. To leave the Unity project out of one build, exclude the task with +`-x compileUnity`; the classes an earlier build produced stay in place. The +work files and the logs of the C# compiler, the translator and the scene +compiler are in `build/cn1-unity/state/unity`. + +[[unity-interop-supported]] +=== What a project can use + +This is the surface the five games in the next sections were built on. It +covers what a 2D game with sprites, tile maps, physics, animation and a +canvas interface is made of. + +==== C# and the base library + +* The language as Unity projects use it: classes, structs, interfaces, enums, + generics, delegates and events, lambdas and closures, iterators, `foreach`, + `ref` and `out` parameters, `switch` on strings and switch expressions, + tuples, string interpolation, exceptions, properties, indexers and operator + overloading. +* Strings: concatenation, `Format` with plain `{0}` items, `Join`, `Split` + on characters or on strings with or without `StringSplitOptions`, + `Substring`, `IndexOf`, `Contains`, `StartsWith`, `EndsWith`, `Replace`, + `Trim`, and the invariant case conversions. +* Numbers: `int.Parse`, `int.TryParse`, `System.Math` and `Mathf`. +* `List` and `Dictionary`. A dictionary enumerates in insertion + order and has `TryGetValue`, `ContainsKey`, `Remove`, `Keys`, `Values` and + `foreach` over its pairs. +* Arrays, including rectangular arrays of any rank with `GetLength`. +* The `Action` and `Func` delegates and these LINQ operators: `Where`, + `Select`, `Any`, `All`, `Count`, `Contains`, `First`, `FirstOrDefault`, + `Last`, `LastOrDefault`, `ElementAt`, `Take`, `Skip`, `Distinct`, `Concat`, + `Reverse`, `ToList` and `ToArray`. + +The script below uses several of these together: a level read from a text +asset in a `Resources` folder into a two-dimensional array, with the tiles it +creates tracked in a dictionary. It builds as written. + +[source,csharp] +---- +include::../demos/common/src/main/snippets/developer-guide/unity-interop.cs[tag=unity-interop-csharp-001,indent=0] +---- + +==== Scripts and objects + +* `MonoBehaviour` with these messages: `Awake`, `OnEnable`, `Start`, + `FixedUpdate`, `Update`, `LateUpdate`, `OnDisable`, `OnDestroy`, and the 2D + collision and trigger messages (`OnCollisionEnter2D`, `OnCollisionStay2D`, + `OnCollisionExit2D` and the three `OnTrigger` equivalents). +* `OnMouseDown` and `OnMouseUp` on an object with a 2D collider, with the + pointer placed in the world by the main camera. `OnApplicationPause(bool)` + and `OnApplicationFocus(bool)` when the game is paused and resumed. +* Coroutines with `yield return null`, `WaitForSeconds`, `WaitForEndOfFrame` + and `WaitForFixedUpdate`, a `Start` written as a coroutine, + `StopCoroutine` and `StopAllCoroutines`. +* `Invoke`, `InvokeRepeating`, `CancelInvoke` and `IsInvoking`. +* `GameObject` and `Transform` hierarchies, `Instantiate` with or without a + position, a rotation and a parent, `Destroy` with an optional delay, `SetActive`, tags and layers, + `Find`, `FindWithTag`, `FindGameObjectsWithTag`, `FindObjectOfType`, + `AddComponent`, and `GetComponent` with its `InChildren` and `InParent` + forms. +* Serialized fields of scripts, as the Inspector stored them: numbers, + strings, enums, vectors, colors, arrays and lists, structs, and references to objects, components, prefabs, sprites, audio + clips and text assets. +* Prefabs, prefab instances placed in a scene with their property overrides, + and nested prefabs. +* `SceneManager.LoadScene` by name or by build index, `Time` with + `timeScale`, `Random`, `Screen.width` and `Screen.height`, `Debug.Log` and + its warning and error forms, and `UnityEngine.Assertions.Assert`. + +==== Rendering + +* `SpriteRenderer` with sorting layers and orders, flipping and tinting. +* Sprites imported as single images, sprite sheets sliced in the Sprite + Editor, pivots and pixels per unit. A polygon sprite is cut to its outline + when the project is built and drawn as that image. +* An orthographic `Camera` with its background color and the conversions + between screen, viewport and world points. + +==== Animation + +* `Animator` with the controllers and clips of the project. The build + compiles each `.controller` and `.anim` file into code, so nothing is + parsed on the device. +* State machines with float, integer, bool and trigger parameters, + transitions with conditions and exit time, and transitions from Any State. +* Curves that animate a sprite renderer's sprite, color and enabled flag, a + transform's position, scale and rotation about z, and whether a GameObject + is active. +* Animation events, which call the method they name on the scripts of the + animated object. +* From a script: `Play`, `CrossFade`, `SetFloat`, `SetInteger`, `SetBool`, + `SetTrigger`, `ResetTrigger` and their getters, `speed`, + `GetCurrentAnimatorStateInfo`, `IsInTransition`, `StringToHash`, and + assigning `runtimeAnimatorController` to swap the controller. + +==== Tile maps + +* `Grid`, `Tilemap` and `TilemapRenderer`, with the cells the Tile Palette + painted. +* `TilemapCollider2D`, alone or merged into outlines by a + `CompositeCollider2D`. +* From a script: `GetTile`, `SetTile`, `HasTile`, `GetSprite`, + `ClearAllTiles`, `WorldToCell`, `CellToWorld` and `GetCellCenterWorld`. + +==== Particles + +* `ParticleSystem`, simulated on the CPU from a seeded sequence, so an effect + plays the same way on every target. +* The main module, emission by rate and in bursts, the emitter shape, and the + velocity, color, size and rotation over lifetime modules, as the scene + stores them. +* From a script: `Play`, `Stop`, `Pause`, `Clear` and `Emit`, the playing + state and `particleCount`, and the `main`, `emission` and + `velocityOverLifetime` modules. + +==== Physics + +* `Rigidbody2D`: dynamic, kinematic and static bodies, velocity, forces and + torques, `MovePosition` and `MoveRotation`, mass, drag, gravity scale, + `constraints` and `collisionDetectionMode`. +* Box, circle, polygon, capsule and edge colliders, tile map and composite + colliders, triggers, and `Collision2D` with its contacts. +* `PlatformEffector2D` for one-way platforms. +* Physics materials (friction and bounciness), the project's gravity and the + layer collision matrix, with `Physics2D.IgnoreLayerCollision` to change it + from a script. +* Casts: `Physics2D.Raycast`, `Linecast`, `CircleCast`, `BoxCast` and + `CapsuleCast`. Each has the form that returns the nearest hit, the `All` + form, the `NonAlloc` form and the form that takes a `ContactFilter2D` with + an array or a list for the results. +* Overlap tests: `OverlapPoint`, `OverlapCircle`, `OverlapBox`, `OverlapArea` + and `OverlapCapsule`, in the same four forms. +* `RaycastHit2D`, `ContactFilter2D` and `LayerMask`, including a `LayerMask` + field set in the Inspector, with `Physics2D.queriesHitTriggers`, + `queriesStartInColliders` and `SyncTransforms`. +* On a collider or a body: `OverlapPoint`, `IsTouching`, `IsTouchingLayers` + and `Cast`, and `Collider2D.bounds`. + +A cast isn't stepped along its path. The runtime solves each one in closed +form, so the distance, the normal and the point it reports are exact to the +rounding of the arithmetic, and a fast or thin shape can't slip past a +collider. A query sees bodies where the last physics step put them; call +`Physics2D.SyncTransforms` after a script moves a transform and before it +asks. + +==== Input + +* `GetKey`, `GetKeyDown` and `GetKeyUp` by `KeyCode` or by name, `anyKey` and + `anyKeyDown`. +* The axes and buttons the project's input settings define on keys, through + `GetAxis`, `GetAxisRaw` and `GetButton`. +* The mouse: `GetMouseButton`, `GetMouseButtonDown`, `GetMouseButtonUp` and + `mousePosition`, and the `Mouse X` and `Mouse Y` axes for its movement. +* Touch: `touchCount`, `touches` and `GetTouch`, with a `Touch` that carries + its `fingerId`, `position`, `deltaPosition`, `tapCount` and a `phase` of + `Began`, `Moved`, `Stationary` or `Ended`. `multiTouchEnabled` and + `simulateMouseWithTouches` work as documented. + +==== User interface, audio and data + +* A `Canvas` in Screen Space, overlay or camera, with a `CanvasScaler` and + `RectTransform` anchoring. +* `Text` with size, style, alignment, wrapping and best fit. +* TextMesh Pro text, as `TMP_Text`, `TextMeshPro` and `TextMeshProUGUI`: + `text`, `SetText`, size, style, alignment, wrapping and auto sizing, drawn + in the platform's font. +* `Image`, simple or filled horizontally or vertically (`fillAmount` drives a + health bar). +* `Button` with its color tint states, the `onClick` calls set up in the + Inspector, and `onClick.AddListener` from a script. +* `AudioSource` and `AudioClip`, played through Codename One's media API: + `Play`, `PlayOneShot`, `Stop`, `Pause`, `loop`, `volume` and + `PlayClipAtPoint`. +* `PlayerPrefs` for integers, floats and strings, saved on the device. +* `Resources.Load` for text assets, images imported as one sprite, audio + clips and prefabs, and `TextAsset` with `text` and `bytes`. + +==== Follow cameras + +* Cinemachine's `CinemachineBrain` and `CinemachineVirtualCamera` with a + `CinemachineFramingTransposer` or a `CinemachineTransposer` body: a camera + that follows its target with the offset, damping, dead zone and soft zone + the scene sets. +* Several virtual cameras by priority, with a cut from one to the next, and + `Follow` assigned from a script. + +[[unity-interop-determinism]] +=== The same game on every target + +Arithmetic on `float` gives the same result on every target, and +`UnityEngine.Random` produces the same sequence from the same seed. A game +that's given a fixed seed and the same input plays the same way in the +simulator, in a native build and in the browser. + +That takes work in two places, because the targets don't agree by default. +The C compiler that builds the iOS and desktop binaries may fuse a multiplication +and an addition into one instruction that rounds once instead of twice, so the +native builds turn that contraction off. A JavaScript number is a double, so +on the JavaScript port every `float` result is rounded back to single +precision before it's used. + +The evidence is a trace comparison. Each game in the next section was run +from a fixed seed with scripted input on three targets: a JVM, the C code +ParparVM generates, and the JavaScript port under Node.js. Each run wrote a +trace of the game's log lines, its object counts and its draw list, with +positions in hundredths of a pixel and rotations in hundredths of a degree. +The three traces of each game are byte for byte the same: 3,503 lines for +Fruitopia, 220 for Asteroids, 852 for Pong, 244 for Snake and 445 for +Sokoban. Fruitopia's run goes through its menu and its lobby, plays a level +to the flag and returns to the lobby, 1,400 frames of animation, particles, +tile map collisions and a follow camera. Asteroids moves +more than 1,100 objects every frame, so one differently rounded multiply +shows up in its trace within a few hundred frames. + +`Random` is seeded from the clock unless you set a seed. To make a run +repeatable, set the `unity.seed` display property before the game starts, for +example `Display.getInstance().setProperty("unity.seed", "7")` at the top of +the main class's `init` method. + +[[unity-interop-games]] +=== Games that run + +Five open source Unity games build and run with no change to their scripts, +scenes or assets. Each is a repository its author published under the MIT +license. They aren't part of Codename One; clone one and import it to try it. + +Every image in this section is a frame the Codename One runtime drew from the +game's own scenes and sprites, at 960 by 540 pixels. + +==== Fruitopia + +https://github.com/thisshrek/Fruitopia[`thisshrek/Fruitopia`], MIT license, +copyright 2024 Mohamed Kharrat. Twelve scripts, 733 lines. Its art is the +Pixel Adventure set by Pixel Frog, released under CC0 and credited in the +game's README. + +A pixel-art platformer with a menu, a lobby and levels to run through: +collect the fruit, avoid the traps, reach the flag. It's the widest test of +the five. Its levels are tile maps with composite colliders and one-way +platforms. Its hero, fruit, traps and menus are driven by 65 `Animator` +components that play 40 clips. The dust behind the hero is a particle +system, the labels are TextMesh Pro text, and a Cinemachine virtual camera +follows the hero. The hero moves through `Rigidbody2D` and finds the ground +with `Physics2D.OverlapBox`. + +image::img/unity-interop-fruitopia-level.png["Fruitopia running on the Codename One runtime: the frog hero in a tile map level with pineapples to collect, a spiked ball and the fruit counter",scaledwidth=70%] + +The text in these frames is in the platform's font rather than the game's +font asset, which is how the runtime draws TextMesh Pro text. + +image::img/unity-interop-fruitopia-menu.png["The Fruitopia menu running on the Codename One runtime: the title and three buttons over a tile map backdrop",scaledwidth=70%] + +The build reports six warnings for this project, and each names something +the project itself is missing or does that Unity also drops. Two levels place +a prefab whose file isn't in the repository, one object carries a script +that isn't under `Assets`, and three objects carry a static class as a +component. The menu, the lobby and the first level play from start to finish. + +==== Asteroids + +https://github.com/sdxsharp/unity-asteroids[`sdxsharp/unity-asteroids`], MIT +license, copyright 2022 `sdxsharp`. Five scripts, 546 lines, saved by Unity +2021.3. + +An arcade shooter that keeps between 1,157 and 1,180 objects alive, most of +them the stars of its background. It exercises rigid bodies driven by +velocity, polygon and circle colliders, collision messages, prefabs +instantiated and destroyed as the game runs, seeded `Random`, the input axes +and a canvas with a score line. + +image::img/unity-interop-asteroids.png["Asteroids running on the Codename One runtime: the ship, three asteroids, the star field and the score line",scaledwidth=70%] + +==== Pong + +https://github.com/MalachiMackie/Unity-Pong[MalachiMackie/Unity-Pong], MIT +license, copyright 2021 Malachi Mackie. Seven scripts, 557 lines, saved by +Unity 2021.1. + +A two-player ball game. It uses prefab instances with property overrides, +physics materials for the bounce, audio clips, C# events and delegates with +tuple payloads, LINQ and switch expressions. + +image::img/unity-interop-pong.png["Pong running on the Codename One runtime: two paddles, the ball and the three score counters",scaledwidth=70%] + +==== Snake + +https://github.com/Wesley-Oliveira/Classic-Snake-Game-Unity-2D[Wesley-Oliveira/Classic-Snake-Game-Unity-2D], +MIT license, copyright 2020 Wesley Oliveira. Two scripts, 184 lines, saved +by Unity 2019.3. + +A grid game with a menu. Its menu is canvas buttons whose `onClick` calls +were set up in the Inspector, its record is kept in `PlayerPrefs`, and the +snake moves on a coroutine that respects `Time.timeScale`. + +image::img/unity-interop-snake.png["Snake running on the Codename One runtime: the snake, the food, the score and the saved record",scaledwidth=70%] + +==== Sokoban + +https://github.com/juwalbose/UnityTileBasedSokoban[`juwalbose/UnityTileBasedSokoban`], +MIT license, copyright 2017 Juwal Bose. One script, 264 lines, saved by +Unity 2017.1. + +A tile puzzle. The level is a text file loaded with `Resources.Load`, split +and parsed into an `int[,]` grid, with the tile objects tracked in a +`Dictionary`. The script creates every tile as a +`GameObject` with a `SpriteRenderer` it adds itself. The hero and the balls +are polygon sprites, and the Restart button reloads the scene through a persistent call to +`SceneManager.LoadScene`. + +image::img/unity-interop-sokoban.png["Sokoban running on the Codename One runtime, part way through the level: the triangular hero, three balls and three target tiles",scaledwidth=70%] + +image::img/unity-interop-sokoban-complete.png["Sokoban running on the Codename One runtime with the level complete: each of the three balls is on a target tile",scaledwidth=70%] + +[[unity-interop-measurements]] +=== Measurements + +The table covers the four smaller games above; Fruitopia follows it. The +numbers measure the game and the +compatibility runtime alone, in a harness that runs the game without a +display: the same scripted run that produced the traces, three runs each, +median reported. A shipped application adds the Codename One port for its +platform on top. + +[cols="2,1,1,1,1",options="header"] +|=== +|Game |Objects alive |Native executable |JavaScript, gzip |Peak memory, native +|Asteroids |1,157 to 1,180 |1.33 MB |246 KB |15.7 MB +|Pong |13 |1.36 MB |244 KB |6.2 MB +|Snake |26 to 31 |1.33 MB |243 KB |5.1 MB +|Sokoban |40 |1.30 MB |241 KB |4.8 MB +|=== + +* *Native executable* is the stripped macOS binary of the game, the runtime + and the Java class library, translated to C by ParparVM and linked with + link-time optimization. +* *JavaScript, gzip* is the script the JavaScript port loads for the same + code, compressed with `gzip -9`. +* *Peak memory* is the most physical memory that native executable held + over an 1,800-frame run, as `/usr/bin/time -l` reports it. + +The translated code is small next to the runtime it runs on. Sokoban's +classes and the whole compatibility runtime pack into a 268 KB jar, of which +the runtime is 252 KB. The games were measured against successive builds of +the runtime, so a difference of 2% or 3% between rows isn't +significant. + +Fruitopia is the largest of the five. These figures are from its scripted +1,400-frame run in the same harness. Between 59 and 80 objects are alive and each frame issues 350 +to 500 draw commands. + +* The compatibility runtime it ships with is 382 KB. The rest of its jar is + the game's own audio. +* The native executable, linked with link-time optimization and stripped, is + 2.67 MB. +* The JavaScript for the same code is 387 KB after `gzip -9`. +* Peak memory over the run was about 10 MB for the native executable, about + 99 MB on a JVM and about 176 MB under Node.js. +* On the JVM the simulation allocated under 2 KB per frame over the whole + run, three scene loads included. + +On speed: Asteroids, the heaviest of the four in the table, spent under half a millisecond +of CPU per frame in the native build, simulation and draw list together, on a +development machine that was busy with other builds. The other three were +below what the timer resolves. Treat that as an order of magnitude and +measure your own game on the device you ship for. + +[[unity-interop-scope]] +=== Scope and porting notes + +The compatibility layer targets 2D games built from sprites, tile maps, 2D +physics, animation controllers, particles, a canvas interface and scripts. +This section lists what's outside that surface +today, what the build does when it meets each item, and what to do about it. + +==== How the build tells you + +A port doesn't fail at run time for a reason the build could have named. +There are four places it speaks up: + +* A `UnityEngine` member the runtime doesn't have is a C# compiler error with + the file and the line. +* A .NET base library member the runtime doesn't have stops the build at the + translation step, with every such member listed at once and, under each, + the script file, the line and the method that uses it. +* A component a scene has and the runtime doesn't is a warning that names the + scene, the GameObject and the component. The component is left out and the + rest of the scene still builds. +* A script method Unity would call by name and the runtime doesn't is a + warning with the class, the method, the file and the line. + +==== Rendering notes + +* *3D.* Meshes, lights, 3D physics, shaders and materials other than the + default sprite material are outside the scope. Their components are left + out with a warning. A perspective camera is made orthographic, and a + rotation about the x-axis or the y-axis is dropped, each with a warning. +* *Animation controllers.* The first layer of a controller is played, and + the build warns when a controller has more. A state that plays a blend tree + shows nothing, and a transition into a sub-state machine is left out, each + with a warning. Give such a state a plain clip, or drive the blend from a + script with `Play` and `CrossFade`. An override controller plays nothing, + with a warning; assign `runtimeAnimatorController` from a script instead. +* *Animated properties.* A clip animates a sprite renderer's sprite, color + and enabled flag, a transform's position, scale and rotation about z, and + a GameObject's active flag. A curve on another property is left out with a + warning that names it; set that property from an animation event or from + `Update`. +* *Timelines* aren't run. Sequence the same steps in a coroutine. +* *Particle modules.* The main, emission, shape, and velocity, color, size + and rotation over lifetime modules are simulated. Another module that's + switched on, such as Noise, Collision, Trails, Sub Emitters or Texture + Sheet Animation, does nothing, and the build warns for each. A system that + emits from a mesh, a donut or a sprite emits from its own position, one + that stretches its particles or draws meshes draws plain sprites facing the + camera, and a system with `Prewarm` on starts empty. Of the modules a script can + reach, `main`, `emission` and `velocityOverLifetime` are in the API. +* *Scripted tiles.* A rule tile or an animated tile isn't run. The cells a + scene was painted with show the sprites the editor chose for them, and a + cell a script sets with such a tile shows its default sprite, with a note. + Use plain tiles for cells a script changes. +* *Line and trail renderers, sprite masks.* The components are left out with + a warning. An effect made of short-lived sprite objects that a script + instantiates and destroys carries over as it is. +* *Sliced and tiled sprites.* A nine-sliced or tiled `SpriteRenderer` or + `Image` draws its sprite stretched, with a warning. A radial fill draws + whole. +* *Built-in and package sprites.* The sprites that belong to the Unity editor + or to a Unity package aren't in the project, so the build draws a plain + rectangle or a white disc in their place and says so. Add your own sprite + to the project to control the look. + +==== Physics notes + +* *Queries.* The casts and overlap tests are listed under + <>. `Physics2D.Distance`, + `Collider2D.Raycast` and `Physics2D.GetRayIntersection` aren't in the API, + so a script that calls one is a compile error. `Physics2D.Raycast` with a + layer mask or a `ContactFilter2D` answers what `Collider2D.Raycast` is + usually asked, and `OverlapPoint` at the pointer's world position does the + job of `GetRayIntersection` in a 2D scene. +* *Edge radius.* An `EdgeCollider2D` with an edge radius collides as a thin + line, with a warning. Use a capsule or a box collider where the thickness + matters. +* *Continuous collision detection.* A body set to Continuous is swept so that + it can't pass through a thin collider. The physics engine decides this for + the whole world: one Continuous body makes it sweep the others too. +* *Joints and other effectors.* Joints are left out with a warning. + `PlatformEffector2D` is the effector that's supported; a collider used by + another effector collides as itself, with a warning. Apply the force of an + area or point effector from `OnTriggerStay2D`. +* *Automatic mass.* A body set to *Use Auto Mass* uses the mass the file + records, with a warning. + +==== Script notes + +* *Messages.* The runtime calls the messages listed under + <>. A method named for + another Unity message, such as `OnBecameInvisible`, `OnMouseDrag` or + `OnGUI`, compiles and is never called. The build warns for each one, with + the class, the method, the file and the line, so the list is in the build + output. A drag can be read in `Update` from `Input.GetMouseButton` and + `Camera.ScreenToWorldPoint`. +* *Coroutine waits.* `WaitUntil`, `WaitWhile` and `WaitForSecondsRealtime` + aren't in the API. A `while` loop around `yield return null` does the same + job. +* *Scenes.* One scene is loaded at a time. `DontDestroyOnLoad` keeps a root + object across a load, with everything below it and its running coroutines + and pending `Invoke` calls. Called for a child object it logs a message and + keeps nothing, as in Unity. +* *The base library.* The runtime implements the part of .NET listed above, + not the whole library. `List.Sort`, `StringBuilder`, format strings such as + `ToString("F2")`, `float.Parse`, `HashSet`, `Queue`, `Stack`, + `DateTime` and `System.Random` are the ones a port meets first. The + translation step lists each use; the usual replacement is a few lines in + the script, such as a hand-written insertion sort or `UnityEngine.Random`. +* *Reflection, `System.IO`, threads, `async` and `await`, networking and + `unsafe` code.* None of these are translated. Keep saved data in + `PlayerPrefs`, load bundled data with `Resources.Load`, and run timed work + in coroutines. Networking belongs on the Java side of the application. +* *Compiled plugins.* Only C# source is translated. The import lists every + `.dll` and native library under `Assets` as not built. Use the source of + the library where its license allows. +* *Packages from the Package Manager.* The build compiles what's under + `Assets`. Copy the source of a package the scripts need into `Assets`. +* *Serialized fields.* A field whose type is a serializable class of your + own keeps the value its constructor gave it, and the build warns with the + field's name. Make the type a struct, which is read, or assign the value in + `Awake`. +* *Editor scripts and custom inspectors.* Scripts in an `Editor` directory + are left out, the way Unity leaves them out of a player build. + +==== Input notes + +* *The Input System package.* The runtime implements the `UnityEngine.Input` + class. A script written against the newer Input System package doesn't + compile; read the same keys, axes and touches through `Input`. +* *Touch phases.* A touch goes through `Began`, `Moved`, `Stationary` and + `Ended`. `Canceled` is never reported, so move the cleanup a + script keeps there into `Ended`. +* *Joystick axes and the scroll wheel.* An axis the input settings bind to a + joystick reads as zero, with a note, and so does `Mouse ScrollWheel`. The + keys bound to the same axis work. +* *Space and Return.* The desktop port reports the space bar and the Enter + key as one fire button, so on the desktop both `KeyCode.Space` and + `KeyCode.Return` read as pressed when either is. A game that gives the two + keys different meanings on the same screen sees both. + +==== User interface notes + +* *Controls.* `Canvas`, `CanvasScaler`, `Text`, `Image`, `Button`, + `GraphicRaycaster` and `EventSystem` are supported. Another control of + Unity's interface package -- a slider, a toggle, an input field, a scroll + view or a layout group -- is left out with a warning + that names the scene, the GameObject and the Unity type, and the build + continues. A script field that pointed at the control is `null`, so guard + the script's use of it. Replace the control with buttons and images, or + show that screen with Codename One components around the game, where every + Codename One control is available. +* *World Space canvases* aren't drawn, with a warning. +* *Fonts.* Text is drawn in the platform's font at the size, style and + alignment the scene sets. A font asset the project names isn't used, which + the build notes for each text. +* *TextMesh Pro.* TextMesh Pro text is drawn the same way: the platform's + font at the size asked for, without the font asset, its material, an + outline or rich text tags, with a note for the scene. Tags in a string are + therefore drawn as written; keep them out of text shown here. +* *Button transitions.* Color tint is supported and changes at once rather + than fading. A sprite swap or animation transition leaves the button + looking the same in every state; it still works. +* *Keyboard navigation between controls* isn't implemented. Controls respond + to the pointer. + +==== Camera notes + +* *Cinemachine.* A virtual camera follows its target through a framing + transposer or a transposer, and the brain cuts between virtual cameras. + Blends, aim behaviors, noise and extensions such as a confiner aren't + implemented, which the build notes once for a scene. A look-ahead time on a + framing transposer is ignored, with a note. To keep the camera inside a + level, clamp its position in `LateUpdate`. + +==== Audio and asset notes + +* *Formats.* Clips are played by each platform's own media player, so try + the game's sound files on every target you build for. +* *Pitch and looping.* A clip plays at its recorded speed whatever + `AudioSource.pitch` says, and a looped clip restarts when it ends, which can + leave a short gap. +* *`Resources`.* `Resources.Load` finds text assets, single-sprite images, + audio clips and prefabs. `LoadAll` isn't in the API. An image sliced into + several sprites isn't found by `Resources.Load`, with a note; refer to its + sprites from a serialized field instead. +* *Asset bundles and the `Addressables` package* aren't supported. Refer to assets from a + scene, a prefab or a `Resources` folder. +* *`Texture2D`* and run-time texture generation aren't in the API. +* *File names.* Images and sounds become application resources, which have + no folders. Two images with the same file name in different folders both + ship, each under a name of its own, and every sprite shows its own image. + Two audio clips with the same file name still collide, and the build warns + about each pair. Rename one. +* *Scripts that can't be components.* A scene that attaches a static class, + an abstract class or a class that isn't a `MonoBehaviour` as a component + gets a warning, and the component is left out, as Unity leaves it out. + +[[unity-interop-troubleshooting]] +=== Troubleshooting + +*The build can't find the .NET SDK.* The message names the three places it +looked: + +---- +src/main/unity holds a Unity project, and compiling its C# scripts needs the .NET SDK, which was not found. +---- + +Install the SDK and put `dotnet` on the `PATH`, or pass +`-Dcn1.unity.dotnet=/path/to/dotnet`. A .NET runtime without the SDK isn't +enough, and the build says so when `dotnet --version` reports no SDK. An SDK +older than version 6 is reported as too old. + +*The C# scripts don't compile.* A script uses a `UnityEngine` type or +member outside the runtime's API. The build prints the C# compiler's errors, +each with its file, line and column: + +---- +The C# scripts in src/main/unity/Assets did not compile: +/work/mygame/common/src/main/unity/Assets/Scripts/Player.cs(14,38): error CS0117: 'Physics2D' does not contain a definition for 'Distance' +---- + +The complete compiler output is in `dotnet.log`. Change the script, or check +<> for the replacement. + +*The translation step fails.* The scripts compiled, and they use +a part of the .NET base library the runtime doesn't have. The translator +lists every missing member, and under it each place that uses it: + +---- +The C# scripts in src/main/unity could not be translated: +error: the runtime library lacks 2 members the code uses: + class com/codename1/unitycompat/system/text/StringBuilder + used by HighScores.cs:17, in HighScores::Print() : string + method com/codename1/unitycompat/system/collections/generic/List_1.Sort()V + used by HighScores.cs:12, in HighScores::Start() : void +---- + +The class after `unitycompat/system/` is the .NET class, and each `used by` +line is the script file, the line and the method to change. + +*A control or a package component is left out.* A scene or prefab has a +component whose script isn't under `Assets`: an interface control outside the +supported set, or a component of a Package Manager package. The build goes on +and warns, once for the component and once for each script field that +referred to it: + +---- +First.unity: GameObject 'Gauge' (component file ID 202) has a UnityEngine.UI.Slider component (script GUID 67db9e8f0e2ae9c40bc1e2b64352a6b4), and the compatibility runtime has no UnityEngine.UI.Slider; the component was left out, and whatever refers to it is left null +First.unity: field gauge of the Keeper script on GameObject 'Keeper' (component file ID 102) refers to a UnityEngine.UI.Slider component (script GUID 67db9e8f0e2ae9c40bc1e2b64352a6b4) of GameObject 'Gauge' (component file ID 202), which was left out; it was left null +---- + +Replace the control, or make the script check the field for `null`. For a +package whose source you have, copying its scripts into `Assets` compiles +them with the project. A scene the build settings list that isn't in the +project is left out with a warning too. + +*A script method is never called.* Unity calls some methods by name, and the +runtime sends the ones listed under +<>. The build warns about +any other: + +---- +Keeper.OnBecameInvisible (Keeper.cs:98): Unity calls this method by its name and the compatibility runtime does not send the OnBecameInvisible message; the method is never called +---- + +*A component is missing in the running game.* Read the warnings of the last +build that compiled the Unity project; a build that found the project +unchanged doesn't repeat them. `translate.log` and `scene-compiler.log` in +`common/target/unity` keep them, each prefixed `warning:` or `note:`. + +*The game looks or behaves differently.* Check, in this order: the warnings +in those two logs; the notes there about substituted sprites and fonts; and +whether a script relies on something listed under +<>. `Debug.Log` output appears in the simulator's +console. + +*The main class wasn't updated by a second import.* The import leaves a main +class you changed after the first import untouched, and says so. Delete the +class and import again to regenerate it. + +=== Independent implementation + +The runtime is an independent implementation of the parts of the Unity +scripting API this chapter lists, written from Unity's public documentation +and from the observable behavior of projects. It contains no Unity code and +no Unity assemblies, and the build doesn't read or need a Unity installation. +The same holds for the two packages the runtime answers for. Its TextMesh Pro +and Cinemachine classes are Codename One's own implementations of the parts +of those documented APIs this chapter lists, and contain no code from either +package. +Codename One isn't affiliated with or endorsed by Unity Technologies. Unity +is a trademark of Unity Technologies. + +Your scripts and assets stay under the terms you hold them under. Assets from +the Unity Asset Store and other third parties have their own license terms. +Check that those terms allow use outside the Unity engine before you ship a +port that includes them. diff --git a/docs/developer-guide/developer-guide.asciidoc b/docs/developer-guide/developer-guide.asciidoc index f257d97f93c..80cbd8c346e 100644 --- a/docs/developer-guide/developer-guide.asciidoc +++ b/docs/developer-guide/developer-guide.asciidoc @@ -245,6 +245,8 @@ include::Working-With-Javascript.asciidoc[] include::Android-Interop.asciidoc[] +include::Unity-Interop.asciidoc[] + include::Working-with-Mac-OS-X.asciidoc[] include::Working-with-Mac-Catalyst.asciidoc[] diff --git a/docs/developer-guide/img/unity-interop-asteroids.png b/docs/developer-guide/img/unity-interop-asteroids.png new file mode 100644 index 0000000000000000000000000000000000000000..8717db67c2eb5aafbe53eaa9bc6e3f07915fed75 GIT binary patch literal 27898 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 B-?ab$ literal 0 HcmV?d00001 diff --git a/docs/developer-guide/img/unity-interop-fruitopia-level.png b/docs/developer-guide/img/unity-interop-fruitopia-level.png new file mode 100644 index 0000000000000000000000000000000000000000..c0ac92a419b28a7cc2da3721f06116b5de9412c5 GIT binary patch literal 104426 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 literal 0 HcmV?d00001 diff --git a/docs/developer-guide/img/unity-interop-fruitopia.png b/docs/developer-guide/img/unity-interop-fruitopia.png new file mode 100644 index 0000000000000000000000000000000000000000..2cc3f5f9fb5a91234efcb6fe4ee436ce35c545dc GIT binary patch literal 90653 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 AwEzGB literal 0 HcmV?d00001 diff --git a/docs/developer-guide/img/unity-interop-sokoban-complete.png b/docs/developer-guide/img/unity-interop-sokoban-complete.png new file mode 100644 index 0000000000000000000000000000000000000000..cb799cbf707995a6002e7a00a98e0f48bde53447 GIT binary patch literal 9329 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 + + + + + diff --git a/maven/build-engine/src/main/java/com/codename1/maven/BackendPackager.java b/maven/build-engine/src/main/java/com/codename1/maven/BackendPackager.java index de09cd6800a..51778bb42b4 100644 --- a/maven/build-engine/src/main/java/com/codename1/maven/BackendPackager.java +++ b/maven/build-engine/src/main/java/com/codename1/maven/BackendPackager.java @@ -839,6 +839,10 @@ private void link(File translated, File binary) // provably miscompiles the output without these. "-fwrapv", "-fno-strict-aliasing", "-fno-builtin-fmod", "-fno-builtin-fmodf", + // Java evaluates a * b + c as two rounded operations. Clang fuses + // them into one multiply-add where the CPU has one (arm64), which + // rounds once and answers differently from every other target. + "-ffp-contract=off", // One section per function and per object, so that the link // below can leave out what nothing reaches. The natives are // compiled whole; without this a server that signs nothing diff --git a/maven/build-engine/src/main/java/com/codename1/maven/GradleConversion.java b/maven/build-engine/src/main/java/com/codename1/maven/GradleConversion.java index 3fc39c854db..70338d573c7 100644 --- a/maven/build-engine/src/main/java/com/codename1/maven/GradleConversion.java +++ b/maven/build-engine/src/main/java/com/codename1/maven/GradleConversion.java @@ -1073,7 +1073,11 @@ private static void copyPlatformResources(ProjectLayout from, ProjectLayout to) static final java.util.Set PLUGIN_SUPPLIED = new java.util.HashSet(java.util.Arrays.asList( "codenameone-core", "codenameone-javase", "java-runtime", "codenameone-backend", "cn1-binaries-javase", "codenameone-css-cli", "codenameone-android", "codenameone-ios", "codenameone-javascript", - "codenameone-maven-plugin")); + "codenameone-maven-plugin", + // Added by the plugin for a project with src/main/unity, at its own + // version, together with the translator of that version: a + // declaration carried over from the pom could name another one. + "codenameone-unity-compat")); /// `build.gradle.kts` lines for the dependencies a module's pom declares /// itself, leaving out the framework, which the plugin adds. A dependency of diff --git a/maven/build-engine/src/main/java/com/codename1/maven/UnityProjectBuilder.java b/maven/build-engine/src/main/java/com/codename1/maven/UnityProjectBuilder.java new file mode 100644 index 00000000000..b003bc887dd --- /dev/null +++ b/maven/build-engine/src/main/java/com/codename1/maven/UnityProjectBuilder.java @@ -0,0 +1,943 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import com.codename1.build.Log; +import com.codename1.builders.BuildException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/// Builds the Unity project of an application module (`src/main/unity`, laid +/// out like any Unity project: `Assets/` and `ProjectSettings/`) into what +/// every Codename One target already knows how to build. Shared by the Maven +/// goal `compile-unity` and the Gradle task. +/// +/// Four steps, each a child process so the build tool's own JVM never loads +/// the translator: +/// +/// 1. the .NET SDK compiles `Assets/**/*.cs` to one assembly, against the +/// reference assemblies of the runtime (never against Unity's own); +/// 2. the CIL translator turns that assembly into class files; +/// 3. the scene compiler turns the scenes and prefabs into Java source +/// (`com.codename1.generated.unity.UnityAppImpl`), because a device build +/// has no reflection to instantiate them with at run time; +/// 4. the classes and the images the scenes draw are installed in the classes +/// directory, where the build tool's own `javac` then compiles the +/// generated source beside them. +/// +/// Steps 1 to 3 are skipped while nothing they read has changed, so a project +/// that is only being run again does not need the .NET SDK at all. Nothing is +/// written outside the build directory: the SDK's first-run files and its +/// package cache are pointed into it unless the environment already names +/// them. +public class UnityProjectBuilder { + + /// The runtime translated scripts run against. An application depends on + /// it like on any library, so every target ships it. + public static final String RUNTIME_ARTIFACT = "codenameone-unity-compat"; + + /// The translator and scene compiler; a build-time tool, never shipped. + public static final String TOOL_ARTIFACT = "codenameone-cil-translator"; + + /// The classifier under which [#RUNTIME_ARTIFACT] publishes the assemblies + /// C# compiles against. A classifier and not resources of the runtime + /// jar, because that jar is merged into the application and the + /// assemblies are megabytes no device has a use for. + public static final String REFERENCES_CLASSIFIER = "references"; + + /// The package of the class the scene compiler generates. + public static final String GENERATED_PACKAGE = "com.codename1.generated.unity"; + + /// The class the scene compiler generates. + public static final String APP_IMPL_CLASS = "UnityAppImpl"; + + /// The lifecycle class a generated main class extends. + public static final String APPLICATION_CLASS = "com.codename1.unitycompat.app.UnityApplication"; + + /// The property both build tools read the `dotnet` executable from. + public static final String DOTNET_PROPERTY = "cn1.unity.dotnet"; + + /// The oldest .NET SDK accepted. The scripts are compiled as C# 9 for + /// `netstandard2.1`; this is the oldest long-term-support SDK whose + /// compiler does that. + static final int MINIMUM_SDK_MAJOR = 6; + + private static final String NETSTANDARD = "netstandard.dll"; + private static final String ENGINE = "UnityEngine.dll"; + private static final String VALUES = "Codename1.UnityValues.dll"; + private static final String SCRIPTS = "Assembly-CSharp.dll"; + private static final String TRANSLATOR_MAIN = "com.codename1.cil.translate.Translator"; + private static final String SCENE_COMPILER_MAIN = "com.codename1.unity.scenecompiler.SceneCompiler"; + /// Bumped when what the steps write changes, so an upgraded plugin does + /// not trust the staging directory an older one left. + private static final String STATE_VERSION = "1"; + + private final File unityDir; + private final File javaOut; + private final File classesOut; + private final File workDir; + private final File runtimeJar; + private final File referencesJar; + private final List toolClasspath; + private final String dotnetSetting; + private final String mainPackage; + private final String mainClass; + private final List sourceRoots; + private final Log log; + private Toolchain toolchain; + + /// The three external steps. An interface so the tests can count and fake + /// them: the logic around them (what is skipped, what is installed, what + /// is removed) must be testable on a machine with no .NET SDK. + interface Toolchain { + /// Compiles the generated C# project; its assembly lands in + /// `bin/Release/netstandard2.1` beside it. + void compileScripts(File csproj, File logFile) throws BuildException; + + /// Translates `assemblies` to class files under `out`. Returns what + /// the translator printed. + List translate(File out, File runtimeClasses, List references, List assemblies, + File logFile) throws BuildException; + + /// Compiles the project's scenes to `javaOut` and copies the images + /// they draw to `resources`. Returns what the compiler printed. + List compileScenes(File project, File javaOut, File resources, List references, + List assemblies, File logFile) throws BuildException; + } + + /// Logs the `warning:` and `note:` lines of a tool's output, and + /// answers how many warnings there were. + private int report(List lines) { + int warnings = 0; + for (String line : lines) { + if (line.startsWith("warning:")) { + log.warn("src/main/unity: " + line.substring("warning:".length()).trim()); + warnings++; + } else if (line.startsWith("note:")) { + log.info("src/main/unity: " + line.substring("note:".length()).trim()); + } + } + return warnings; + } + + /// @param unityDir `src/main/unity` + /// @param javaOut generated sources (`target/generated-sources/unity`) + /// @param classesOut where the translated classes and the images go (the classes directory) + /// @param buildDir `target`; everything else is written under its `unity` directory + /// @param runtimeJar the `codenameone-unity-compat` jar + /// @param referencesJar its `references` classifier + /// @param toolClasspath the `codenameone-cil-translator` jar and its dependencies + /// @param dotnetSetting the `dotnet` executable or its directory, or null to look for it + /// @param mainPackage `codename1.packageName`, or null + /// @param mainClass `codename1.mainName`, or null + /// @param sourceRoots the project's own source roots, to see whether the main class exists + public UnityProjectBuilder(File unityDir, File javaOut, File classesOut, File buildDir, File runtimeJar, + File referencesJar, List toolClasspath, String dotnetSetting, + String mainPackage, String mainClass, List sourceRoots, Log log) { + this.unityDir = unityDir; + this.javaOut = javaOut; + this.classesOut = classesOut; + this.workDir = new File(buildDir, "unity"); + this.runtimeJar = runtimeJar; + this.referencesJar = referencesJar; + this.toolClasspath = toolClasspath == null ? new ArrayList() : new ArrayList(toolClasspath); + this.dotnetSetting = dotnetSetting; + this.mainPackage = mainPackage; + this.mainClass = mainClass; + this.sourceRoots = sourceRoots == null ? new ArrayList() : new ArrayList(sourceRoots); + this.log = log; + } + + /// A Unity project has both directories. `Assets` alone is not enough to + /// go by: the scenes to build, the physics settings and the input axes + /// are all read from `ProjectSettings`. + public static boolean isUnityProject(File unityDir) { + return unityDir != null && new File(unityDir, "Assets").isDirectory() + && new File(unityDir, "ProjectSettings").isDirectory(); + } + + void setToolchain(Toolchain toolchain) { + this.toolchain = toolchain; + } + + /// Builds the project unless nothing changed since the last run. Returns + /// false when there is no Unity project. + public boolean run() throws BuildException { + if (!isUnityProject(unityDir)) { + if (unityDir != null && unityDir.isDirectory()) { + log.warn(unityDir + " is not a Unity project (it needs both Assets and ProjectSettings); ignored"); + } + return false; + } + if (runtimeJar == null || !runtimeJar.isFile()) { + throw new BuildException("src/main/unity exists but the " + RUNTIME_ARTIFACT + + " dependency is missing; add it to the common module, or run cn1:import-unity-project," + + " which adds it."); + } + if (referencesJar == null || !referencesJar.isFile()) { + throw new BuildException("The reference assemblies of " + RUNTIME_ARTIFACT + " (its '" + + REFERENCES_CLASSIFIER + "' classifier) could not be resolved; the C# scripts in" + + " src/main/unity cannot be compiled without them."); + } + File state = new File(workDir, "state.txt"); + File staged = new File(workDir, "app"); + File resources = new File(workDir, "resources"); + File runtimeClasses = new File(workDir, "runtime"); + File appImpl = new File(javaOut, GENERATED_PACKAGE.replace('.', File.separatorChar) + File.separator + + APP_IMPL_CLASS + ".java"); + String digest = digest(); + if (digest.equals(read(state)) && staged.isDirectory() && appImpl.isFile()) { + log.debug("Unity project unchanged; not compiled again"); + } else { + // Deleted first: a step that fails must not leave the next build + // believing the staging directory matches the sources. + deleteQuietly(state); + compile(staged, resources, runtimeClasses); + if (!appImpl.isFile()) { + throw new BuildException("The scene compiler wrote no " + appImpl + " (log: " + + new File(workDir, "scene-compiler.log") + ")"); + } + write(state, digest); + } + install(staged, resources, runtimeClasses); + writeMainClass(); + return true; + } + + private void compile(File staged, File resources, File runtimeClasses) throws BuildException { + if (toolClasspath.isEmpty() && toolchain == null) { + throw new BuildException("The " + TOOL_ARTIFACT + " tool could not be resolved; the Unity project in" + + " src/main/unity cannot be translated without it."); + } + Toolchain tools = toolchain != null ? toolchain : new ForkedToolchain(); + File refs = new File(workDir, "ref"); + File cs = new File(workDir, "cs"); + delete(refs); + delete(cs); + delete(staged); + delete(resources); + delete(runtimeClasses); + // The scene compiler's output is the only thing of ours in javaOut + // that a changed project can make stale. + delete(new File(javaOut, GENERATED_PACKAGE.replace('.', File.separatorChar))); + mkdirs(refs); + mkdirs(cs); + mkdirs(staged); + mkdirs(resources); + mkdirs(javaOut); + try { + extract(referencesJar, refs, ".dll"); + extract(runtimeJar, runtimeClasses, ".class"); + } catch (IOException e) { + throw new BuildException("Cannot unpack the Unity runtime: " + e.getMessage(), e); + } + File netstandard = new File(refs, NETSTANDARD); + File engine = new File(refs, ENGINE); + File values = new File(refs, VALUES); + for (File f : new File[] {netstandard, engine, values}) { + if (!f.isFile()) { + throw new BuildException(referencesJar + " has no " + f.getName() + + "; it is not the reference assemblies of " + RUNTIME_ARTIFACT); + } + } + File csproj = new File(cs, "Assembly-CSharp.csproj"); + write(csproj, csproj(new File(unityDir, "Assets"), netstandard, engine, values)); + + log.info("Compiling the C# scripts of " + unityDir); + tools.compileScripts(csproj, new File(workDir, "dotnet.log")); + File scripts = new File(cs, "bin/Release/netstandard2.1/" + SCRIPTS); + if (!scripts.isFile()) { + throw new BuildException("The C# build produced no " + scripts + " (log: " + + new File(workDir, "dotnet.log") + ")"); + } + List references = new ArrayList(); + references.add(engine); + references.add(netstandard); + // The value types are translated along with the scripts, not only + // referenced: the translator keeps the names of what it translates and + // maps what it merely references onto the hand-written runtime. + List assemblies = new ArrayList(); + assemblies.add(values); + assemblies.add(scripts); + // Both tools say what they could not carry over, a line each; a + // build that kept those to its log files would be a game that + // differs from the one in Unity with nothing to say why. + int warnings = report(tools.translate(staged, runtimeClasses, references, assemblies, + new File(workDir, "translate.log"))); + List sceneAssemblies = new ArrayList(); + sceneAssemblies.add(scripts); + sceneAssemblies.add(values); + warnings += report(tools.compileScenes(unityDir, javaOut, resources, references, sceneAssemblies, + new File(workDir, "scene-compiler.log"))); + log.info("Compiled the Unity project" + (warnings == 0 ? "" : " with " + warnings + + " warning(s): what they name will not behave as it does in Unity")); + } + + /// The C# project that compiles the scripts where they are. Its + /// references are the three assemblies and nothing else: with the + /// implicit framework reference left on, the SDK would download the + /// `netstandard` targeting pack from NuGet on every clean build, and fail + /// offline. + static String csproj(File assets, File netstandard, File engine, File values) { + String a = msbuild(assets.getAbsolutePath()); + return "\n" + + " \n" + + " Library\n" + + " netstandard2.1\n" + + " 9.0\n" + + " disable\n" + + " disable\n" + + " Assembly-CSharp\n" + + " true\n" + + " portable\n" + + " true\n" + + " false\n" + + " false\n" + + " true\n" + + " $(NoWarn);CS0649;CS0414;CS0169;CS0108;CS0114;CS0618\n" + + " \n" + + " \n" + // Editor scripts are not part of a player build. + + " \n" + + reference("netstandard", netstandard) + + reference("UnityEngine", engine) + + reference("Codename1.UnityValues", values) + + " \n" + + "\n"; + } + + private static String reference(String name, File assembly) { + return " \n" + + " " + msbuild(assembly.getAbsolutePath()) + "\n" + + " false\n" + + " \n"; + } + + /// A path as MSBuild reads it literally, inside XML: its own special + /// characters percent-encoded, then XML's escaped. + static String msbuild(String path) { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < path.length(); i++) { + char c = path.charAt(i); + switch (c) { + case '%': + b.append("%25"); + break; + case '$': + b.append("%24"); + break; + case '@': + b.append("%40"); + break; + case ';': + b.append("%3B"); + break; + case '\'': + b.append("%27"); + break; + case '*': + b.append("%2A"); + break; + case '?': + b.append("%3F"); + break; + case '&': + b.append("&"); + break; + case '<': + b.append("<"); + break; + case '>': + b.append(">"); + break; + case '"': + b.append("""); + break; + case '\\': + b.append('/'); + break; + default: + b.append(c); + break; + } + } + return b.toString(); + } + + /// Copies what the steps staged into the classes directory, and removes + /// what an earlier run installed and this one did not: a script deleted + /// from the project must not stay behind as a class. + /// + /// This runs on every build, also when nothing was compiled: the classes + /// directory is the build tool's, and a clean of it alone must not lose + /// the game. + private void install(File staged, File resources, File runtimeClasses) throws BuildException { + File record = new File(workDir, "installed.txt"); + Set previous = new LinkedHashSet(); + String recorded = read(record); + if (recorded != null) { + for (String line : recorded.split("\n")) { + if (line.length() > 0) { + previous.add(line); + } + } + } + Set installed = new TreeSet(); + List files = new ArrayList(); + collect(staged, "", files); + int classes = 0; + try { + for (String rel : files) { + // The runtime jar already ships the value types, translated + // from the same assembly. A second copy in the application + // would be one more definition of the class for each target's + // packaging to pick between. + if (new File(runtimeClasses, rel).isFile()) { + continue; + } + copyIfChanged(new File(staged, rel), new File(classesOut, rel)); + installed.add(rel); + classes++; + } + files.clear(); + collect(resources, "", files); + for (String rel : files) { + copyIfChanged(new File(resources, rel), new File(classesOut, rel)); + installed.add(rel); + } + } catch (IOException e) { + throw new BuildException("Cannot install the Unity project into " + classesOut + ": " + e.getMessage(), e); + } + for (String rel : previous) { + if (!installed.contains(rel)) { + deleteQuietly(new File(classesOut, rel)); + } + } + StringBuilder sb = new StringBuilder(); + for (String rel : installed) { + sb.append(rel).append('\n'); + } + write(record, sb.toString()); + log.debug("Installed " + classes + " translated classes and " + files.size() + " images in " + classesOut); + } + + private static void copyIfChanged(File from, File to) throws IOException { + if (to.isFile() && to.length() == from.length() && to.lastModified() >= from.lastModified()) { + return; + } + File parent = to.getParentFile(); + if (parent != null) { + mkdirs(parent); + } + Files.copy(from.toPath(), to.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + private File generatedMainClass() { + return new File(javaOut, mainPackage.replace('.', File.separatorChar) + File.separator + mainClass + ".java"); + } + + /// True when the application has its own main class, in which case the + /// generated one is deleted: left in the generated sources next to the + /// developer's class, javac would see two definitions until a clean build. + private boolean removeGeneratedMainClassIfWritten() { + String rel = mainPackage.replace('.', File.separatorChar) + File.separator + mainClass; + for (File root : sourceRoots) { + if (root.equals(javaOut)) { + continue; + } + if (new File(root, rel + ".java").isFile() || new File(root, rel + ".kt").isFile()) { + File generated = generatedMainClass(); + if (generated.isFile() && !generated.delete()) { + log.warn("Cannot delete the generated " + generated + "; the application now has its own"); + } + return true; + } + } + return false; + } + + /// Generates the Codename One main class when the project does not have + /// one, so a Unity project builds without any Codename One code of its + /// own. Checked on every run and not only when the project changed: a + /// main class written or deleted since the last build must be noticed. + private void writeMainClass() throws BuildException { + if (mainPackage == null || mainClass == null || mainPackage.length() == 0 || mainClass.length() == 0) { + return; + } + if (removeGeneratedMainClassIfWritten()) { + return; + } + File out = generatedMainClass(); + String src = mainClassSource(mainPackage, mainClass, + "// Generated by the Codename One Unity build: the application's Codename One\n" + + "// entry point. Write this class yourself to customize startup.\n", ""); + if (src.equals(read(out))) { + return; + } + write(out, src); + } + + /// The source of a main class that runs the project's first scene. The + /// subclass names the generated class itself, as source: the runtime jar + /// was compiled before the project existed and has no other way to reach + /// it without reflection. + static String mainClassSource(String pkg, String cls, String fileComment, String classComment) { + return fileComment + + "package " + pkg + ";\n\n" + + classComment + + "public class " + cls + " extends " + APPLICATION_CLASS + " {\n" + + " @Override\n" + + " protected void installProject() {\n" + + " " + GENERATED_PACKAGE + "." + APP_IMPL_CLASS + ".install();\n" + + " }\n" + + "}\n"; + } + + private String digest() { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + List entries = new ArrayList(); + collectStamped(new File(unityDir, "Assets"), "Assets/", entries); + collectStamped(new File(unityDir, "ProjectSettings"), "ProjectSettings/", entries); + Collections.sort(entries); + for (String e : entries) { + md.update(e.getBytes(StandardCharsets.UTF_8)); + md.update((byte) '\n'); + } + StringBuilder tools = new StringBuilder(STATE_VERSION); + stamp(tools, runtimeJar); + stamp(tools, referencesJar); + for (File f : toolClasspath) { + stamp(tools, f); + } + md.update(tools.toString().getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(); + for (byte b : md.digest()) { + sb.append(Integer.toHexString((b & 0xff) | 0x100).substring(1)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + // No digest, no skipping: an empty string never equals a stored one. + return ""; + } + } + + private static void stamp(StringBuilder sb, File f) { + sb.append('|').append(f.getName()).append(':').append(f.length()).append(':').append(f.lastModified()); + } + + private static void collectStamped(File dir, String prefix, List out) { + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File f : files) { + if (f.isDirectory()) { + collectStamped(f, prefix + f.getName() + "/", out); + } else { + out.add(prefix + f.getName() + ":" + f.length() + ":" + f.lastModified()); + } + } + } + + private static void collect(File dir, String prefix, List out) { + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File f : files) { + if (f.isDirectory()) { + collect(f, prefix + f.getName() + "/", out); + } else { + out.add(prefix + f.getName()); + } + } + } + + /// Unpacks the entries of `jar` whose names end in `suffix`. + private static void extract(File jar, File into, String suffix) throws IOException { + String root = into.getCanonicalPath() + File.separator; + try (ZipFile zip = new ZipFile(jar)) { + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + if (e.isDirectory() || !e.getName().endsWith(suffix)) { + continue; + } + File out = new File(into, e.getName()); + // An entry named with ".." would be written outside the + // directory; no jar of ours has one. + if (!out.getCanonicalPath().startsWith(root)) { + throw new IOException(jar + " has an entry outside its root: " + e.getName()); + } + File parent = out.getParentFile(); + if (parent != null) { + mkdirs(parent); + } + try (InputStream in = zip.getInputStream(e)) { + Files.copy(in, out.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + } + } + + private static void mkdirs(File dir) { + if (!dir.isDirectory() && !dir.mkdirs() && !dir.isDirectory()) { + throw new IllegalStateException("Cannot create " + dir); + } + } + + private static void delete(File f) { + File[] children = f.listFiles(); + if (children != null) { + for (File c : children) { + delete(c); + } + } + deleteQuietly(f); + } + + private static void deleteQuietly(File f) { + try { + Files.deleteIfExists(f.toPath()); + } catch (IOException e) { + // Whatever is in the way is reported by the step that then fails + // to write here, with the path. + return; + } + } + + /// The text of a file, or null when it cannot be read. + private static String read(File f) { + if (!f.isFile()) { + return null; + } + try { + return new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8); + } catch (IOException e) { + return null; + } + } + + private static void write(File f, String text) throws BuildException { + try { + File parent = f.getParentFile(); + if (parent != null) { + mkdirs(parent); + } + Files.write(f.toPath(), text.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new BuildException("Cannot write " + f + ": " + e.getMessage(), e); + } + } + + // ------------------------------------------------------------- the SDK + + static boolean isWindows() { + return File.separatorChar == '\\'; + } + + /// The `dotnet` executable: the explicit setting, then `DOTNET_ROOT`, then + /// the `PATH`. The setting may name the executable, the directory it is + /// in, or a bare command to look for on the `PATH`. + /// + /// @param setting the value of [#DOTNET_PROPERTY], or null + /// @param env the environment to read `DOTNET_ROOT` and `PATH` from + static File locateDotnet(String setting, Map env) throws BuildException { + String exe = isWindows() ? "dotnet.exe" : "dotnet"; + String path = env.get("PATH"); + if (path == null) { + // Windows spells it Path, and its environment is case insensitive + // to everything but a Java map. + path = env.get("Path"); + } + if (setting != null && setting.trim().length() > 0) { + File f = new File(setting.trim()); + if (f.isDirectory() && new File(f, exe).isFile()) { + return new File(f, exe); + } + if (f.isFile()) { + return f; + } + File found = f.getParent() == null ? onPath(f.getName(), path) : null; + if (found != null) { + return found; + } + throw new BuildException(DOTNET_PROPERTY + " names " + setting + ", which is neither the dotnet" + + " executable nor the directory it is in. " + installHint()); + } + String root = env.get("DOTNET_ROOT"); + if (root != null && root.length() > 0 && new File(root, exe).isFile()) { + return new File(root, exe); + } + File found = onPath(exe, path); + if (found != null) { + return found; + } + throw new BuildException("src/main/unity holds a Unity project, and compiling its C# scripts needs the" + + " .NET SDK, which was not found. Looked at the " + DOTNET_PROPERTY + " property (not set), the" + + " DOTNET_ROOT environment variable (" + (root == null || root.length() == 0 ? "not set" : root) + + ") and the PATH. " + installHint()); + } + + private static String installHint() { + return "Install the .NET SDK " + MINIMUM_SDK_MAJOR + " or newer from https://dotnet.microsoft.com/download" + + " and either put dotnet on the PATH or pass -D" + DOTNET_PROPERTY + "=/path/to/dotnet."; + } + + private static File onPath(String name, String path) { + if (path == null) { + return null; + } + for (String dir : path.split(File.pathSeparator)) { + if (dir.length() == 0) { + continue; + } + File f = new File(dir, name); + if (f.isFile()) { + return f; + } + } + return null; + } + + /// The major version in what `dotnet --version` printed, or -1. The + /// version is the first line that starts with a number; an SDK that has + /// something to say first (a first-run notice) says it on other lines. + static int sdkMajor(List output) { + for (String line : output) { + String t = line.trim(); + int dot = t.indexOf('.'); + if (dot <= 0) { + continue; + } + int major = 0; + boolean digits = true; + for (int i = 0; i < dot; i++) { + char c = t.charAt(i); + if (c < '0' || c > '9' || i > 4) { + digits = false; + break; + } + major = major * 10 + (c - '0'); + } + if (digits) { + return major; + } + } + return -1; + } + + /// The forked implementation: the .NET CLI and this JVM's own `java`. + private final class ForkedToolchain implements Toolchain { + private File dotnet() throws BuildException { + File dotnet = locateDotnet(dotnetSetting, System.getenv()); + File logFile = new File(workDir, "dotnet-version.log"); + List cmd = new ArrayList(); + cmd.add(dotnet.getAbsolutePath()); + cmd.add("--version"); + int exit = exec(cmd, logFile, true); + List out = lines(logFile); + int major = sdkMajor(out); + if (exit != 0 || major < 0) { + // The host without an SDK answers --version with an error: + // a runtime-only installation is the usual way to get here. + throw new BuildException(dotnet + " did not report an SDK version (exit code " + exit + "): " + + tail(out, 5) + "\nA .NET runtime alone cannot compile C#. " + installHint()); + } + if (major < MINIMUM_SDK_MAJOR) { + throw new BuildException(dotnet + " is the .NET SDK " + out.get(0).trim() + ", which is too old to" + + " compile the scripts in src/main/unity. " + installHint()); + } + return dotnet; + } + + public void compileScripts(File csproj, File logFile) throws BuildException { + List cmd = new ArrayList(); + cmd.add(dotnet().getAbsolutePath()); + cmd.add("build"); + cmd.add("-c"); + cmd.add("Release"); + cmd.add("--nologo"); + // No build servers left running behind a build that has ended: + // they would hold the build directory open, on Windows against + // the next clean. + cmd.add("-nodeReuse:false"); + cmd.add("-p:UseSharedCompilation=false"); + cmd.add(csproj.getAbsolutePath()); + if (exec(cmd, logFile, true) != 0) { + List out = lines(logFile); + List errors = new ArrayList(); + for (String line : out) { + if (line.contains(": error ") && !errors.contains(line.trim())) { + errors.add(line.trim()); + } + } + throw new BuildException("The C# scripts in src/main/unity/Assets did not compile:\n" + + (errors.isEmpty() ? tail(out, 40) : tail(errors, 40)) + "\n(full log: " + logFile + ")\n" + + "A script that compiles in Unity and not here uses a part of the UnityEngine API the" + + " compatibility runtime does not have."); + } + } + + public List translate(File out, File runtimeClasses, List references, List assemblies, + File logFile) throws BuildException { + List cmd = java(TRANSLATOR_MAIN); + cmd.add("--out"); + cmd.add(out.getAbsolutePath()); + cmd.add("--runtime"); + cmd.add(runtimeClasses.getAbsolutePath()); + for (File r : references) { + cmd.add("--ref"); + cmd.add(r.getAbsolutePath()); + } + for (File a : assemblies) { + cmd.add(a.getAbsolutePath()); + } + if (exec(cmd, logFile, false) != 0) { + throw new BuildException("The C# scripts in src/main/unity could not be translated:\n" + + tail(lines(logFile), 40) + "\n(full log: " + logFile + ")"); + } + return lines(logFile); + } + + public List compileScenes(File project, File sceneJavaOut, File resources, List references, + List assemblies, File logFile) throws BuildException { + List cmd = java(SCENE_COMPILER_MAIN); + cmd.add("--project"); + cmd.add(project.getAbsolutePath()); + cmd.add("--out"); + cmd.add(sceneJavaOut.getAbsolutePath()); + cmd.add("--resources"); + cmd.add(resources.getAbsolutePath()); + for (File r : references) { + cmd.add("--ref"); + cmd.add(r.getAbsolutePath()); + } + for (File a : assemblies) { + cmd.add(a.getAbsolutePath()); + } + int exit = exec(cmd, logFile, false); + List out = lines(logFile); + if (exit != 0) { + throw new BuildException("The scenes in src/main/unity could not be compiled:\n" + tail(out, 40) + + "\n(full log: " + logFile + ")"); + } + return out; + } + + private List java(String mainClassName) { + List cmd = new ArrayList(); + cmd.add(new File(new File(System.getProperty("java.home"), "bin"), isWindows() ? "java.exe" : "java") + .getAbsolutePath()); + // The scene compiler reads images with ImageIO; without this a + // desktop JVM shows an icon in the dock for the length of a build. + cmd.add("-Djava.awt.headless=true"); + cmd.add("-cp"); + StringBuilder cp = new StringBuilder(); + for (File f : toolClasspath) { + if (cp.length() > 0) { + cp.append(File.pathSeparatorChar); + } + cp.append(f.getAbsolutePath()); + } + cmd.add(cp.toString()); + cmd.add(mainClassName); + return cmd; + } + + /// Runs a command to its end, its output in `logFile`. + private int exec(List cmd, File logFile, boolean sdk) throws BuildException { + mkdirs(workDir); + ProcessBuilder pb = new ProcessBuilder(cmd); + // Not the project directory: a global.json there, written for the + // Unity editor's own tooling, would pick the SDK for us. + pb.directory(workDir); + pb.redirectErrorStream(true); + pb.redirectOutput(logFile); + if (sdk) { + Map env = pb.environment(); + env.put("DOTNET_CLI_TELEMETRY_OPTOUT", "1"); + env.put("DOTNET_NOLOGO", "1"); + env.put("DOTNET_CLI_USE_MSBUILD_SERVER", "0"); + env.put("MSBUILDDISABLENODEREUSE", "1"); + // Left alone when set: a CI machine that points these at a + // cache of its own has said where they belong. + if (!env.containsKey("DOTNET_CLI_HOME")) { + env.put("DOTNET_CLI_HOME", new File(workDir, "dotnet-home").getAbsolutePath()); + } + if (!env.containsKey("NUGET_PACKAGES")) { + env.put("NUGET_PACKAGES", new File(workDir, "nuget-packages").getAbsolutePath()); + } + } + log.debug("Running " + cmd); + try { + return pb.start().waitFor(); + } catch (IOException e) { + throw new BuildException("Cannot run " + cmd.get(0) + ": " + e.getMessage(), e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BuildException("Interrupted while running " + cmd.get(0), e); + } + } + } + + private static List lines(File f) { + String text = read(f); + List out = new ArrayList(); + if (text != null) { + for (String line : text.split("\n")) { + out.add(line.endsWith("\r") ? line.substring(0, line.length() - 1) : line); + } + } + return out; + } + + private static String tail(List lines, int count) { + StringBuilder sb = new StringBuilder(); + for (int i = Math.max(0, lines.size() - count); i < lines.size(); i++) { + if (sb.length() > 0) { + sb.append('\n'); + } + sb.append(lines.get(i)); + } + return sb.toString(); + } +} diff --git a/maven/build-engine/src/main/java/com/codename1/maven/UnityProjectImporter.java b/maven/build-engine/src/main/java/com/codename1/maven/UnityProjectImporter.java new file mode 100644 index 00000000000..35de35ee6cc --- /dev/null +++ b/maven/build-engine/src/main/java/com/codename1/maven/UnityProjectImporter.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import com.codename1.build.Log; +import com.codename1.builders.BuildException; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/// Copies a Unity project into a Codename One application +/// (`common/src/main/unity`) and makes the application's main class run it. +/// Shared by the Maven goal `import-unity-project` and the Gradle task. +/// +/// Only `Assets` and `ProjectSettings` are copied. They are the project; +/// `Library`, `Temp`, `obj` and `Logs` are what the Unity editor derives from +/// them, are routinely gigabytes, and hold the editor's own compiled +/// assemblies, which must never reach a build of ours. +/// +/// The copy is a mirror of the Unity project, not a fork of it: importing +/// again replaces every file an earlier import copied and removes the ones +/// the project no longer has. The game is edited in Unity and imported again. +public final class UnityProjectImporter { + + /// The directories of a Unity project that are its source. + static final String[] COPIED = {"Assets", "ProjectSettings"}; + + /// Names never copied, wherever they are: version-control metadata and + /// what an operating system leaves in a directory it has shown. + static final List SKIPPED = Collections.unmodifiableList(Arrays.asList( + ".git", ".svn", ".hg", ".vs", ".idea", ".DS_Store", "Thumbs.db", "obj", "Temp", "Library")); + + /// Compiled code and native libraries a project carries as files. None of + /// it is translated: there is C# source for none of it. + private static final String[] BINARY_PLUGINS = {".dll", ".so", ".a", ".dylib", ".bundle", ".aar", ".jar"}; + + /// The list of files the last import copied, kept in the target so the + /// next import can remove what the project dropped. + static final String IMPORT_RECORD = ".unity-import-files"; + + public static final class Result { + public int copiedFiles; + public int removedFiles; + public int scripts; + public int scenes; + /// `productName` of `ProjectSettings/ProjectSettings.asset`, or null. + public String productName; + /// Files that will be ignored by the build, each with the reason. + public final List ignored = new ArrayList(); + } + + private final Log log; + + public UnityProjectImporter(Log log) { + this.log = log; + } + + /// @param source the Unity project: the directory that holds `Assets` + /// @param commonDir the application's common module + /// @param mainPackage `codename1.packageName`, or null to leave the main class alone + /// @param mainClass `codename1.mainName`, or null to leave the main class alone + public Result importProject(File source, File commonDir, String mainPackage, String mainClass) + throws BuildException { + if (source == null || !source.isDirectory()) { + throw new BuildException(source + " is not a directory; name the Unity project to import, the directory" + + " that holds Assets and ProjectSettings"); + } + if (!UnityProjectBuilder.isUnityProject(source)) { + throw new BuildException(source + " is not a Unity project: it needs both an Assets and a" + + " ProjectSettings directory"); + } + File target = new File(commonDir, "src/main/unity"); + Result r = new Result(); + try { + if (target.getCanonicalFile().equals(source.getCanonicalFile())) { + throw new BuildException(source + " is already this application's src/main/unity"); + } + Set previous = readImportRecord(target); + Set imported = new TreeSet(); + for (String name : COPIED) { + copy(new File(source, name), new File(target, name), name, imported, r); + } + for (String path : previous) { + if (!imported.contains(path)) { + File stale = new File(target, path); + if (stale.isFile() && stale.delete()) { + r.removedFiles++; + } + } + } + writeImportRecord(target, imported); + r.productName = productName(new File(source, "ProjectSettings/ProjectSettings.asset")); + if (mainPackage != null && mainClass != null && mainPackage.length() > 0 && mainClass.length() > 0) { + writeEntryPoint(new File(commonDir, "src/main/java"), mainPackage, mainClass); + } + } catch (IOException e) { + throw new BuildException("Cannot import " + source + ": " + e.getMessage(), e); + } + return r; + } + + private void copy(File src, File dest, String path, Set imported, Result r) throws IOException { + if (src.isDirectory()) { + File[] children = src.listFiles(); + if (children == null) { + return; + } + for (File f : children) { + if (SKIPPED.contains(f.getName())) { + continue; + } + copy(f, new File(dest, f.getName()), path + "/" + f.getName(), imported, r); + } + return; + } + File parent = dest.getParentFile(); + if (parent != null && !parent.isDirectory() && !parent.mkdirs() && !parent.isDirectory()) { + throw new IOException("cannot create " + parent); + } + Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING); + imported.add(path); + r.copiedFiles++; + String name = src.getName(); + if (name.endsWith(".cs")) { + r.scripts++; + } else if (name.endsWith(".unity")) { + r.scenes++; + } else { + for (String suffix : BINARY_PLUGINS) { + if (name.endsWith(suffix)) { + r.ignored.add(path + " (compiled code: only C# source is translated)"); + break; + } + } + } + } + + /// The value of the `productName:` line of a project settings file, or + /// null. The file is YAML when the project serializes assets as text, + /// which is the only form the build reads either. + static String productName(File settings) { + if (!settings.isFile()) { + return null; + } + try { + for (String line : Files.readAllLines(settings.toPath(), StandardCharsets.ISO_8859_1)) { + String t = line.trim(); + if (t.startsWith("productName:")) { + String value = t.substring("productName:".length()).trim(); + return value.length() == 0 ? null : value; + } + } + } catch (IOException e) { + return null; + } + return null; + } + + private void writeEntryPoint(File javaDir, String pkg, String cls) throws IOException { + File f = new File(javaDir, pkg.replace('.', File.separatorChar) + File.separator + cls + ".java"); + File parent = f.getParentFile(); + if (parent != null && !parent.isDirectory() && !parent.mkdirs() && !parent.isDirectory()) { + throw new IOException("cannot create " + parent); + } + String src = UnityProjectBuilder.mainClassSource(pkg, cls, "", + "/// The Codename One entry point: runs the first scene of the imported Unity project.\n"); + byte[] bytes = src.getBytes(StandardCharsets.UTF_8); + if (f.isFile()) { + File backup = new File(f.getPath() + ".pre-unity-import"); + boolean unchanged = Arrays.equals(bytes, Files.readAllBytes(f.toPath())); + if (backup.exists()) { + // A repeated import: the backup already holds the application's + // original class, and overwriting it would lose the only copy. + // The file itself is what an earlier import wrote -- unless the + // developer has customized it since, and then it is kept. + if (!unchanged) { + log.warn(f.getName() + " was changed after an earlier import and was left as it is; " + + "delete it and import again to regenerate it"); + return; + } + } else if (!unchanged) { + Files.copy(f.toPath(), backup.toPath()); + log.info("Saved the previous " + f.getName() + " as " + backup.getName()); + } + } + Files.write(f.toPath(), bytes); + } + + private static Set readImportRecord(File target) throws IOException { + Set out = new TreeSet(); + File record = new File(target, IMPORT_RECORD); + if (record.isFile()) { + for (String line : Files.readAllLines(record.toPath(), StandardCharsets.UTF_8)) { + // Only paths under the two copied directories: the record is + // a file in the project, and a line edited into it must not + // be able to name a file elsewhere for deletion. + if (line.indexOf("..") < 0 && (line.startsWith("Assets/") || line.startsWith("ProjectSettings/"))) { + out.add(line); + } + } + } + return out; + } + + private static void writeImportRecord(File target, Set imported) throws IOException { + StringBuilder sb = new StringBuilder(); + for (String path : imported) { + sb.append(path).append('\n'); + } + Files.write(new File(target, IMPORT_RECORD).toPath(), sb.toString().getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/maven/build-engine/src/test/java/com/codename1/maven/GradleConversionTest.java b/maven/build-engine/src/test/java/com/codename1/maven/GradleConversionTest.java index e44a1cfcb1c..498f6b76f44 100644 --- a/maven/build-engine/src/test/java/com/codename1/maven/GradleConversionTest.java +++ b/maven/build-engine/src/test/java/com/codename1/maven/GradleConversionTest.java @@ -422,6 +422,31 @@ void mavenPathPatternsMatchAsMavenDoes() { assertFalse(GradleConversion.antMatches("a?c.txt", "a/c.txt")); } + /// The Gradle plugin adds the Unity runtime to a project that has + /// `src/main/unity`, at its own version and with the translator of that + /// version; the declaration cn1:import-unity-project wrote into the pom + /// must not be carried over as a second one, while the Unity project + /// itself has to arrive. + @Test + void theUnityRuntimeIsLeftToThePluginAndTheProjectIsCopied() throws Exception { + File mvn = mavenApp(UNTOUCHED_API); + touch(mvn, "common/pom.xml", "com.acmemvnapp" + + "1.0mvnapp-common" + + "com.codenameonecodenameone-unity-compat" + + "1" + + "org.exampleutil2.0" + + ""); + touch(mvn, "common/src/main/unity/Assets/Scripts/Player.cs", "class Player {}"); + touch(mvn, "common/src/main/unity/ProjectSettings/ProjectSettings.asset", "x"); + File out = new File(tmp.toFile(), "out"); + converter().convert(mvn, out, "1.0"); + String build = read(new File(out, "build.gradle.kts")); + assertFalse(build.contains("codenameone-unity-compat"), build); + assertTrue(build.contains(" implementation(\"org.example:util:2.0\")"), build); + assertTrue(new File(out, "src/main/unity/Assets/Scripts/Player.cs").isFile()); + assertTrue(new File(out, "src/main/unity/ProjectSettings/ProjectSettings.asset").isFile()); + } + @Test void platformModuleDependenciesAreNotDroppedSilently() throws Exception { File mvn = mavenApp(UNTOUCHED_API); diff --git a/maven/build-engine/src/test/java/com/codename1/maven/UnityProjectBuilderTest.java b/maven/build-engine/src/test/java/com/codename1/maven/UnityProjectBuilderTest.java new file mode 100644 index 00000000000..3e359e4eef2 --- /dev/null +++ b/maven/build-engine/src/test/java/com/codename1/maven/UnityProjectBuilderTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import com.codename1.build.SystemStreamLog; +import com.codename1.builders.BuildException; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/// The build of `src/main/unity`, with the three external steps faked: what +/// is under test is everything around them -- when they run, what reaches the +/// classes directory, what leaves it -- and that has to be testable on a +/// machine with no .NET SDK. The steps themselves run for real in +/// `maven/integration-tests/unity-compat-project-test.sh`. +public class UnityProjectBuilderTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private File unity; + private File target; + private File classes; + private File generated; + private File sources; + private File runtimeJar; + private File referencesJar; + private FakeToolchain tools; + private final List warned = new ArrayList(); + + /// Stands in for the SDK, the translator and the scene compiler, writing + /// the files the real ones would. + private static final class FakeToolchain implements UnityProjectBuilder.Toolchain { + int compiled; + int translated; + int scenes; + boolean failTranslation; + List classNames = new ArrayList(Arrays.asList("global/Player", "global/Shot", + "UnityEngine/Vector2")); + String csproj; + File runtimeClasses; + + public void compileScripts(File csprojFile, File logFile) throws BuildException { + compiled++; + try { + csproj = new String(Files.readAllBytes(csprojFile.toPath()), StandardCharsets.UTF_8); + File dll = new File(csprojFile.getParentFile(), "bin/Release/netstandard2.1/Assembly-CSharp.dll"); + dll.getParentFile().mkdirs(); + Files.write(dll.toPath(), new byte[] {'M', 'Z'}); + } catch (IOException e) { + throw new BuildException(e.getMessage(), e); + } + } + + public List translate(File out, File runtime, List references, List assemblies, + File logFile) throws BuildException { + translated++; + runtimeClasses = runtime; + if (failTranslation) { + throw new BuildException("translation failed"); + } + try { + for (String name : classNames) { + File f = new File(out, name + ".class"); + f.getParentFile().mkdirs(); + Files.write(f.toPath(), name.getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + throw new BuildException(e.getMessage(), e); + } + return Arrays.asList("3 classes", "warning: Player.OnGUI (Player.cs:7): never called"); + } + + public List compileScenes(File project, File javaOut, File resources, List references, + List assemblies, File logFile) throws BuildException { + scenes++; + try { + File impl = new File(javaOut, "com/codename1/generated/unity/UnityAppImpl.java"); + impl.getParentFile().mkdirs(); + Files.write(impl.toPath(), "package com.codename1.generated.unity;\n".getBytes(StandardCharsets.UTF_8)); + resources.mkdirs(); + Files.write(new File(resources, "sheet.png").toPath(), new byte[] {1, 2, 3}); + } catch (IOException e) { + throw new BuildException(e.getMessage(), e); + } + return Arrays.asList("note: mouse axes read as zero", "warning: a particle system was left out", + "wrote UnityAppImpl.java"); + } + } + + @Before + public void project() throws Exception { + File root = tmp.newFolder("app"); + unity = new File(root, "src/main/unity"); + write(new File(unity, "Assets/Scripts/Player.cs"), "class Player {}"); + write(new File(unity, "Assets/Scenes/Main.unity"), "%YAML 1.1"); + write(new File(unity, "ProjectSettings/EditorBuildSettings.asset"), "scenes"); + target = new File(root, "target"); + classes = new File(target, "classes"); + generated = new File(target, "generated-sources/unity"); + sources = new File(root, "src/main/java"); + runtimeJar = zip(new File(root, "codenameone-unity-compat.jar"), "UnityEngine/Vector2.class", + "com/codename1/unitycompat/unityengine/UnityRuntime.class", "META-INF/MANIFEST.MF"); + referencesJar = zip(new File(root, "codenameone-unity-compat-references.jar"), "netstandard.dll", + "UnityEngine.dll", "Codename1.UnityValues.dll"); + tools = new FakeToolchain(); + } + + private UnityProjectBuilder builder() { + return builder(runtimeJar, referencesJar); + } + + private UnityProjectBuilder builder(File runtime, File references) { + UnityProjectBuilder b = new UnityProjectBuilder(unity, generated, classes, target, runtime, references, + Collections.emptyList(), null, "com.acme.game", "MyGame", Collections.singletonList(sources), + new SystemStreamLog() { + @Override + public void warn(CharSequence content) { + warned.add(content.toString()); + super.warn(content); + } + }); + b.setToolchain(tools); + return b; + } + + private static void write(File f, String text) throws IOException { + f.getParentFile().mkdirs(); + Files.write(f.toPath(), text.getBytes(StandardCharsets.UTF_8)); + } + + private static File zip(File f, String... entries) throws IOException { + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f))) { + for (String e : entries) { + out.putNextEntry(new ZipEntry(e)); + out.write(e.getBytes(StandardCharsets.UTF_8)); + out.closeEntry(); + } + } + return f; + } + + @Test + public void aProjectWithoutUnitySourcesIsLeftAlone() throws Exception { + File empty = tmp.newFolder("plain"); + assertFalse(UnityProjectBuilder.isUnityProject(new File(empty, "src/main/unity"))); + assertFalse(UnityProjectBuilder.isUnityProject(null)); + unity = new File(empty, "src/main/unity"); + // Not even the dependency is asked for: a project that is not a Unity + // project must build exactly as it did before the goal existed. + assertFalse(builder(null, null).run()); + assertEquals(0, tools.compiled); + assertFalse(target.exists()); + } + + /// `Assets` alone is some other directory of that name: the scenes to + /// build are listed in `ProjectSettings`. + @Test + public void assetsAloneAreNotAProject() throws Exception { + File dir = tmp.newFolder("half"); + assertTrue(new File(dir, "Assets").mkdirs()); + assertFalse(UnityProjectBuilder.isUnityProject(dir)); + unity = dir; + assertFalse(builder().run()); + assertEquals(0, tools.compiled); + } + + @Test + public void installsClassesImagesSceneSourceAndMainClass() throws Exception { + assertTrue(builder().run()); + assertTrue(new File(classes, "global/Player.class").isFile()); + assertTrue(new File(classes, "global/Shot.class").isFile()); + assertTrue(new File(classes, "sheet.png").isFile()); + assertTrue(new File(generated, "com/codename1/generated/unity/UnityAppImpl.java").isFile()); + String main = new String(Files.readAllBytes(new File(generated, "com/acme/game/MyGame.java").toPath()), + StandardCharsets.UTF_8); + assertTrue(main, main.contains("public class MyGame extends com.codename1.unitycompat.app.UnityApplication")); + assertTrue(main, main.contains("com.codename1.generated.unity.UnityAppImpl.install();")); + // The translator checks the scripts against the runtime's classes, + // which it reads from a directory: the jar was unpacked for it. + assertTrue(new File(tools.runtimeClasses, "com/codename1/unitycompat/unityengine/UnityRuntime.class").isFile()); + assertFalse("only classes are unpacked", new File(tools.runtimeClasses, "META-INF/MANIFEST.MF").exists()); + } + + /// What the translator could not carry over reaches the build's own + /// output beside what the scene compiler could not, and not only a log + /// file under `target`. + @Test + public void warningsOfBothToolsAreReported() throws Exception { + assertTrue(builder().run()); + assertEquals(Arrays.asList("src/main/unity: Player.OnGUI (Player.cs:7): never called", + "src/main/unity: a particle system was left out"), warned); + } + + /// The value types are translated with the scripts, and the runtime jar + /// already ships them: the application must not carry a second copy. + @Test + public void classesTheRuntimeShipsAreNotInstalledAgain() throws Exception { + assertTrue(builder().run()); + assertFalse(new File(classes, "UnityEngine/Vector2.class").exists()); + } + + @Test + public void anUnchangedProjectIsNotCompiledAgain() throws Exception { + assertTrue(builder().run()); + assertTrue(builder().run()); + assertEquals(1, tools.compiled); + assertEquals(1, tools.translated); + assertEquals(1, tools.scenes); + // A changed script is. + write(new File(unity, "Assets/Scripts/Player.cs"), "class Player { int lives; }"); + assertTrue(builder().run()); + assertEquals(2, tools.compiled); + // And so is a changed setting, which only the scene compiler reads. + write(new File(unity, "ProjectSettings/EditorBuildSettings.asset"), "scenes: two"); + assertTrue(builder().run()); + assertEquals(3, tools.scenes); + } + + /// A new runtime translates differently and checks against different + /// classes: the staging directory of the old one cannot be trusted. + @Test + public void aNewRuntimeJarCompilesAgain() throws Exception { + assertTrue(builder().run()); + zip(runtimeJar, "UnityEngine/Vector2.class", "UnityEngine/Vector3.class"); + assertTrue(builder().run()); + assertEquals(2, tools.translated); + } + + /// The classes directory is the build tool's to clean; cleaning it alone + /// must not need the .NET SDK to get the game back. + @Test + public void aCleanedClassesDirectoryIsRefilledWithoutCompiling() throws Exception { + assertTrue(builder().run()); + assertTrue(new File(classes, "global/Player.class").delete()); + assertTrue(new File(classes, "sheet.png").delete()); + assertTrue(builder().run()); + assertEquals(1, tools.compiled); + assertTrue(new File(classes, "global/Player.class").isFile()); + assertTrue(new File(classes, "sheet.png").isFile()); + } + + @Test + public void aScriptDeletedFromTheProjectLeavesTheClasses() throws Exception { + assertTrue(builder().run()); + write(new File(classes, "com/acme/game/Own.class"), "the application's own class"); + tools.classNames.remove("global/Shot"); + write(new File(unity, "Assets/Scripts/Player.cs"), "class Player { /* no shots */ }"); + assertTrue(builder().run()); + assertFalse(new File(classes, "global/Shot.class").exists()); + assertTrue(new File(classes, "global/Player.class").isFile()); + assertTrue("only what this build installed is removed", new File(classes, "com/acme/game/Own.class").isFile()); + } + + /// A build that fails half way has replaced part of the staging + /// directory; the next one must not skip on the strength of the old state. + @Test + public void aFailedBuildIsNotRememberedAsDone() throws Exception { + assertTrue(builder().run()); + write(new File(unity, "Assets/Scripts/Player.cs"), "class Player { broken }"); + tools.failTranslation = true; + try { + builder().run(); + fail("the translation failure was swallowed"); + } catch (BuildException expected) { + assertEquals("translation failed", expected.getMessage()); + } + tools.failTranslation = false; + assertTrue(builder().run()); + assertEquals(3, tools.compiled); + assertTrue(new File(classes, "global/Player.class").isFile()); + } + + @Test + public void theApplicationsOwnMainClassRetiresTheGeneratedOne() throws Exception { + assertTrue(builder().run()); + File generatedMain = new File(generated, "com/acme/game/MyGame.java"); + assertTrue(generatedMain.isFile()); + File own = new File(sources, "com/acme/game/MyGame.java"); + write(own, "package com.acme.game; public class MyGame {}"); + assertTrue(builder().run()); + assertFalse("javac would see two MyGame classes", generatedMain.exists()); + // Deleted again, the generated one comes back, with nothing compiled. + assertTrue(own.delete()); + assertTrue(builder().run()); + assertTrue(generatedMain.isFile()); + assertEquals(1, tools.compiled); + } + + @Test + public void aMissingRuntimeDependencyIsNamed() throws Exception { + try { + builder(null, referencesJar).run(); + fail(); + } catch (BuildException e) { + assertTrue(e.getMessage(), e.getMessage().contains("codenameone-unity-compat dependency is missing")); + assertTrue(e.getMessage(), e.getMessage().contains("cn1:import-unity-project")); + } + try { + builder(runtimeJar, null).run(); + fail(); + } catch (BuildException e) { + assertTrue(e.getMessage(), e.getMessage().contains("'references' classifier")); + } + } + + @Test + public void aReferencesJarWithoutTheAssembliesIsRejected() throws Exception { + zip(referencesJar, "netstandard.dll"); + try { + builder().run(); + fail(); + } catch (BuildException e) { + assertTrue(e.getMessage(), e.getMessage().contains("has no UnityEngine.dll")); + } + assertEquals("nothing was compiled against half the references", 0, tools.compiled); + } + + /// The generated project references the three assemblies and nothing + /// else, so the SDK has nothing to download; and it compiles the scripts + /// where they are, without the editor's. + @Test + public void theCSharpProjectIsSelfContained() throws Exception { + assertTrue(builder().run()); + String p = tools.csproj; + assertTrue(p, p.contains("true")); + assertTrue(p, p.contains("netstandard2.1")); + assertTrue(p, p.contains("/Assets/**/*.cs\" Exclude=\"")); + assertTrue(p, p.contains("/Assets/**/Editor/**/*.cs\"")); + for (String name : new String[] {"netstandard", "UnityEngine", "Codename1.UnityValues"}) { + assertTrue(p, p.contains("")); + assertTrue(p, p.contains("/unity/ref/" + name + ".dll")); + } + assertFalse("no package may be needed", p.contains("PackageReference")); + } + + /// A path is data to MSBuild only when its special characters are + /// encoded; unencoded, `$(x)` in a directory name is a property reference. + @Test + public void pathsAreLiteralToMsbuild() { + assertEquals("/Users/a & b/50%25/%24(x)/it%27s%3B%40", UnityProjectBuilder.msbuild("/Users/a & b/50%/$(x)/it's;@")); + assertEquals("C:/games/My Game", UnityProjectBuilder.msbuild("C:\\games\\My Game")); + } + + @Test + public void findsDotnetWhereItIsNamedThenDotnetRootThenThePath() throws Exception { + String exe = UnityProjectBuilder.isWindows() ? "dotnet.exe" : "dotnet"; + File named = new File(tmp.newFolder("named"), exe); + File rooted = new File(tmp.newFolder("rooted"), exe); + File onPath = new File(tmp.newFolder("onpath"), exe); + for (File f : new File[] {named, rooted, onPath}) { + write(f, ""); + } + Map env = new HashMap(); + env.put("DOTNET_ROOT", rooted.getParent()); + env.put("PATH", tmp.getRoot() + File.pathSeparator + onPath.getParent()); + assertEquals(named, UnityProjectBuilder.locateDotnet(named.getPath(), env)); + assertEquals("the directory it is in names it too", named, + UnityProjectBuilder.locateDotnet(named.getParent(), env)); + assertEquals(rooted, UnityProjectBuilder.locateDotnet(null, env)); + assertEquals(rooted, UnityProjectBuilder.locateDotnet(" ", env)); + env.remove("DOTNET_ROOT"); + assertEquals(onPath, UnityProjectBuilder.locateDotnet(null, env)); + assertEquals("a bare command is looked for on the PATH", onPath, + UnityProjectBuilder.locateDotnet(exe, env)); + } + + @Test + public void aMissingSdkSaysWhereItLookedAndWhatToDo() { + Map env = new HashMap(); + env.put("PATH", tmp.getRoot().getPath()); + try { + UnityProjectBuilder.locateDotnet(null, env); + fail(); + } catch (BuildException e) { + String m = e.getMessage(); + assertTrue(m, m.contains(".NET SDK, which was not found")); + assertTrue(m, m.contains("DOTNET_ROOT environment variable (not set)")); + assertTrue(m, m.contains("-Dcn1.unity.dotnet=/path/to/dotnet")); + assertTrue(m, m.contains("https://dotnet.microsoft.com/download")); + } + // A setting that names nothing is an error, not a reason to fall back: + // the developer said which SDK to use. + try { + UnityProjectBuilder.locateDotnet(new File(tmp.getRoot(), "no/such/dotnet").getPath(), env); + fail(); + } catch (BuildException e) { + assertTrue(e.getMessage(), e.getMessage().startsWith("cn1.unity.dotnet names ")); + } + } + + @Test + public void readsTheSdkVersion() { + assertEquals(10, UnityProjectBuilder.sdkMajor(Arrays.asList("10.0.401"))); + assertEquals(8, UnityProjectBuilder.sdkMajor(Arrays.asList("", "Welcome to .NET!", " 8.0.100-rc.1 "))); + assertEquals(-1, UnityProjectBuilder.sdkMajor(Arrays.asList("No .NET SDKs were found.", "Download: x"))); + assertEquals(-1, UnityProjectBuilder.sdkMajor(Collections.emptyList())); + } + + /// The real toolchain against a stand-in `dotnet`: an SDK too old for the + /// scripts, and a runtime with no SDK at all, are both refused before + /// anything is compiled, each with what to install. + @Test + public void refusesAnSdkThatIsTooOldOrIsNoSdk() throws Exception { + Assume.assumeFalse("the stand-in is a shell script", UnityProjectBuilder.isWindows()); + File old = script("old", "echo 3.1.426"); + File runtimeOnly = script("runtime", "echo 'No .NET SDKs were found.'; exit 145"); + assertRefused(old, "is the .NET SDK 3.1.426, which is too old"); + assertRefused(runtimeOnly, "A .NET runtime alone cannot compile C#"); + } + + private File script(String dir, String body) throws IOException { + File f = new File(tmp.newFolder(dir), "dotnet"); + write(f, "#!/bin/sh\n" + body + "\n"); + assertTrue(f.setExecutable(true)); + return f; + } + + private void assertRefused(File dotnet, String expected) { + UnityProjectBuilder b = new UnityProjectBuilder(unity, generated, classes, target, runtimeJar, referencesJar, + Collections.singletonList(runtimeJar), dotnet.getPath(), null, null, null, new SystemStreamLog()); + try { + b.run(); + fail("built with " + dotnet); + } catch (BuildException e) { + assertTrue(e.getMessage(), e.getMessage().contains(expected)); + assertTrue(e.getMessage(), e.getMessage().contains("Install the .NET SDK")); + } + } +} diff --git a/maven/build-engine/src/test/java/com/codename1/maven/UnityProjectImporterTest.java b/maven/build-engine/src/test/java/com/codename1/maven/UnityProjectImporterTest.java new file mode 100644 index 00000000000..553f72608df --- /dev/null +++ b/maven/build-engine/src/test/java/com/codename1/maven/UnityProjectImporterTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import com.codename1.build.SystemStreamLog; +import com.codename1.builders.BuildException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class UnityProjectImporterTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private File game; + private File common; + private File unity; + + @Before + public void project() throws Exception { + game = tmp.newFolder("MyGame"); + write(new File(game, "Assets/Scripts/Player.cs"), "class Player {}"); + write(new File(game, "Assets/Scripts/Player.cs.meta"), "guid: 1"); + write(new File(game, "Assets/Scenes/Main.unity"), "%YAML 1.1"); + write(new File(game, "Assets/Plugins/Fast.dll"), "MZ"); + write(new File(game, "ProjectSettings/ProjectSettings.asset"), + "PlayerSettings:\n companyName: Acme\n productName: Rock Fall\n"); + // What the editor derives, and what version control keeps. + write(new File(game, "Library/ScriptAssemblies/Assembly-CSharp.dll"), "MZ"); + write(new File(game, "Temp/lock"), ""); + write(new File(game, "obj/Debug/x.cache"), ""); + write(new File(game, ".git/HEAD"), "ref"); + write(new File(game, "Assets/.git/HEAD"), "a nested checkout"); + write(new File(game, "Assets/Scripts/obj/Debug/y.cache"), ""); + write(new File(game, "Packages/manifest.json"), "{}"); + common = tmp.newFolder("app", "common"); + unity = new File(common, "src/main/unity"); + } + + private static void write(File f, String text) throws IOException { + f.getParentFile().mkdirs(); + Files.write(f.toPath(), text.getBytes(StandardCharsets.UTF_8)); + } + + private static String read(File f) throws IOException { + return new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8); + } + + private UnityProjectImporter.Result importIt() throws BuildException { + return new UnityProjectImporter(new SystemStreamLog()).importProject(game, common, "com.acme.game", "MyGame"); + } + + @Test + public void copiesTheProjectAndNothingTheEditorDerives() throws Exception { + UnityProjectImporter.Result r = importIt(); + assertTrue(new File(unity, "Assets/Scripts/Player.cs").isFile()); + assertTrue("the .meta files carry the guids scenes refer to scripts by", + new File(unity, "Assets/Scripts/Player.cs.meta").isFile()); + assertTrue(new File(unity, "Assets/Scenes/Main.unity").isFile()); + assertTrue(new File(unity, "ProjectSettings/ProjectSettings.asset").isFile()); + for (String never : new String[] {"Library", "Temp", "obj", ".git", "Packages", "Assets/.git", + "Assets/Scripts/obj"}) { + assertFalse(never + " was copied", new File(unity, never).exists()); + } + assertEquals(5, r.copiedFiles); + assertEquals(1, r.scripts); + assertEquals(1, r.scenes); + assertEquals("Rock Fall", r.productName); + assertEquals(1, r.ignored.size()); + assertTrue(r.ignored.get(0), r.ignored.get(0).startsWith("Assets/Plugins/Fast.dll (compiled code")); + assertTrue(UnityProjectBuilder.isUnityProject(unity)); + } + + @Test + public void writesTheEntryPointAndKeepsTheApplicationsOwn() throws Exception { + File main = new File(common, "src/main/java/com/acme/game/MyGame.java"); + write(main, "package com.acme.game; public class MyGame { /* the archetype's */ }"); + importIt(); + String src = read(main); + assertTrue(src, src.startsWith("package com.acme.game;\n\n/// The Codename One entry point")); + assertTrue(src, src.contains("public class MyGame extends com.codename1.unitycompat.app.UnityApplication {")); + assertTrue(src, src.contains("com.codename1.generated.unity.UnityAppImpl.install();")); + File backup = new File(main.getPath() + ".pre-unity-import"); + assertTrue(read(backup).contains("the archetype's")); + // Importing twice keeps the backup of the application's own class; + // the second run must not replace it with what the first one wrote. + importIt(); + assertTrue(read(backup).contains("the archetype's")); + // A class customized after the import is kept as it is. + write(main, src + "// mine\n"); + importIt(); + assertTrue(read(main).endsWith("// mine\n")); + } + + /// The copy mirrors the Unity project: what the project dropped goes, + /// what changed is taken, and what the import never copied stays. + @Test + public void importingAgainMirrorsTheProject() throws Exception { + importIt(); + File own = new File(unity, "Assets/Scripts/Added.cs"); + write(own, "class Added {}"); + assertTrue(new File(game, "Assets/Scenes/Main.unity").delete()); + write(new File(game, "Assets/Scripts/Player.cs"), "class Player { int lives; }"); + UnityProjectImporter.Result r = importIt(); + assertEquals(1, r.removedFiles); + assertFalse(new File(unity, "Assets/Scenes/Main.unity").exists()); + assertTrue(read(new File(unity, "Assets/Scripts/Player.cs")).contains("lives")); + assertTrue("a file the import did not copy is not its to remove", own.isFile()); + } + + /// The record of what was imported is a file in the project; a line + /// edited into it must not name a file elsewhere for deletion. + @Test + public void theImportRecordCannotNameFilesOutsideTheCopy() throws Exception { + importIt(); + File outside = new File(common, "src/main/java/Keep.java"); + write(outside, "class Keep {}"); + File record = new File(unity, UnityProjectImporter.IMPORT_RECORD); + write(record, read(record) + "../java/Keep.java\nAssets/../../java/Keep.java\n"); + importIt(); + assertTrue(outside.isFile()); + } + + @Test + public void refusesWhatIsNotAUnityProject() throws Exception { + File notOne = tmp.newFolder("notes"); + try { + new UnityProjectImporter(new SystemStreamLog()).importProject(notOne, common, "a", "B"); + fail(); + } catch (BuildException e) { + assertTrue(e.getMessage(), e.getMessage().contains("is not a Unity project")); + } + assertFalse("nothing is created for a project that was not imported", unity.exists()); + } + + @Test + public void readsNoProductNameFromABinarySettingsFile() throws Exception { + File settings = new File(game, "ProjectSettings/ProjectSettings.asset"); + Files.write(settings.toPath(), new byte[] {0, 1, 2, (byte) 0xff, 3}); + assertNull(UnityProjectImporter.productName(settings)); + assertNull(UnityProjectImporter.productName(new File(game, "ProjectSettings/None.asset"))); + } +} diff --git a/maven/cil-translator/pom.xml b/maven/cil-translator/pom.xml new file mode 100644 index 00000000000..58566b0f431 --- /dev/null +++ b/maven/cil-translator/pom.xml @@ -0,0 +1,206 @@ + + + + + com.codenameone + codenameone + 8.0-SNAPSHOT + + 4.0.0 + + codenameone-cil-translator + 8.0-SNAPSHOT + jar + codenameone-cil-translator + + Build-time translator from .NET CIL to JVM bytecode. Reads the + assemblies a C# compiler produced (ECMA-335 metadata and method + bodies) and writes class files every Codename One target already + knows how to build: the simulator and Android run them as they are, + ParparVM translates them on to C and JavaScript. Depends on nothing + but ASM. + + + + UTF-8 + 1.8 + 1.8 + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.6.1 + + + copy-translator-dependencies + process-classes + + copy-dependencies + + + runtime + ${project.build.directory}/dependency + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + ${project.basedir}/../.. + ${project.build.directory}/unity-tests + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.7.3.4 + + Max + Low + true + true + ${project.build.directory} + ${project.basedir}/spotbugs-exclude.xml + + + + spotbugs + verify + + spotbugs + check + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.21.0 + + false + false + 1.8 + + ${project.basedir}/../core-unittests/pmd.xml + + + + + pmd + verify + + pmd + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.0 + + ${project.basedir}/../core-unittests/checkstyle.xml + ${project.build.sourceEncoding} + false + false + false + false + ${project.build.directory}/checkstyle-result.xml + xml + + + + validate + verify + + checkstyle + + + + + checkstyle-zero-errors + verify + + check + + + true + error + 0 + true + + + + + + + + + + org.ow2.asm + asm + + + + com.codenameone + codenameone-core + test + + + junit + junit + test + + + + org.junit.vintage + junit-vintage-engine + ${junit.jupiter.version} + test + + + diff --git a/maven/cil-translator/spotbugs-exclude.xml b/maven/cil-translator/spotbugs-exclude.xml new file mode 100644 index 00000000000..718bf0b7eaf --- /dev/null +++ b/maven/cil-translator/spotbugs-exclude.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilAssembly.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilAssembly.java new file mode 100644 index 00000000000..f8f641ccb28 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilAssembly.java @@ -0,0 +1,749 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/// The logical layer of one assembly: its types, their members, and the +/// answer to "what does this token mean". +/// +/// An assembly is read once, eagerly, apart from method bodies. References to +/// types in other assemblies are resolved by name through the [Universe] the +/// assembly was loaded into, which is the only way to learn whether a +/// referenced type is a class, a struct or an interface: a reference row +/// records the name and nothing else. +public final class CilAssembly { + + public static final int TYPE_INTERFACE = 0x20; + public static final int TYPE_ABSTRACT = 0x80; + public static final int TYPE_SEALED = 0x100; + + public static final int METHOD_ACCESS_MASK = 0x7; + public static final int METHOD_PRIVATE = 0x1; + public static final int METHOD_STATIC = 0x10; + public static final int METHOD_FINAL = 0x20; + public static final int METHOD_VIRTUAL = 0x40; + public static final int METHOD_NEW_SLOT = 0x100; + public static final int METHOD_ABSTRACT = 0x400; + + public static final int FIELD_STATIC = 0x10; + public static final int FIELD_LITERAL = 0x40; + + public static final int SEMANTICS_ADD_ON = 0x8; + public static final int SEMANTICS_REMOVE_ON = 0x10; + + /// A type this assembly defines. + public static final class TypeDef { + public final CilAssembly assembly; + public final int row; + public final String namespace; + public final String name; + public final int flags; + public TypeDef enclosing; + /// Null only for `System.Object` and for interfaces. + public CilType baseType; + public final List interfaces = new ArrayList(); + public final List fields = new ArrayList(); + public final List methods = new ArrayList(); + public final List methodImpls = new ArrayList(); + public int genericParamCount; + private String fullName; + + TypeDef(CilAssembly assembly, int row, String namespace, String name, int flags) { + this.assembly = assembly; + this.row = row; + this.namespace = namespace; + this.name = name; + this.flags = flags; + } + + public String fullName() { + if (fullName == null) { + if (enclosing != null) { + fullName = enclosing.fullName() + "/" + name; + } else { + fullName = namespace.length() == 0 ? name : namespace + "." + name; + } + } + return fullName; + } + + public boolean isInterface() { + return (flags & TYPE_INTERFACE) != 0; + } + + public boolean isAbstract() { + return (flags & TYPE_ABSTRACT) != 0; + } + + private String baseName() { + return baseType == null ? null : baseType.typeName(); + } + + public boolean isEnum() { + return "System.Enum".equals(baseName()); + } + + /// A struct or an enum. `System.Enum` itself derives from + /// `System.ValueType` and is a class. + public boolean isValueType() { + String base = baseName(); + if ("System.Enum".equals(base)) { + return true; + } + return "System.ValueType".equals(base) && !"System.Enum".equals(fullName()); + } + + public boolean isDelegate() { + return "System.MulticastDelegate".equals(baseName()); + } + + /// The integer type an enum is stored as: the type of its one + /// instance field. + public CilType enumUnderlyingType() { + for (FieldDef f : fields) { + if (!f.isStatic()) { + return f.type; + } + } + throw new CilFormatException(assembly.source() + ": enum " + fullName() + " has no value field"); + } + + public CilType asType() { + CilType builtin = CilType.builtin(fullName()); + return builtin != null ? builtin : CilType.named(fullName(), isValueType()); + } + + public FieldDef field(String fieldName) { + for (FieldDef f : fields) { + if (f.name.equals(fieldName)) { + return f; + } + } + return null; + } + + @Override + public String toString() { + return fullName(); + } + } + + public static final class FieldDef { + public final TypeDef owner; + public final int row; + public final String name; + public final int flags; + public final CilType type; + /// Where the initial data of a mapped field lives, or 0. + public int rva; + /// The value of a literal field of an integer type -- a member of an + /// enum, a `const int` -- or null. + public Long constant; + + FieldDef(TypeDef owner, int row, String name, int flags, CilType type) { + this.owner = owner; + this.row = row; + this.name = name; + this.flags = flags; + this.type = type; + } + + public boolean isStatic() { + return (flags & FIELD_STATIC) != 0; + } + + public boolean isLiteral() { + return (flags & FIELD_LITERAL) != 0; + } + + public int token() { + return (MetadataReader.FIELD << 24) | row; + } + } + + public static final class MethodDef { + public final TypeDef owner; + public final int row; + public final String name; + public final int flags; + public final int rva; + public final MethodSig sig; + public int genericParamCount; + /// `SEMANTICS_*` bits when this is an event accessor, else 0. + public int semantics; + /// Name of the event or property this method is an accessor of. + public String associationName; + private MethodBody body; + + MethodDef(TypeDef owner, int row, String name, int flags, int rva, MethodSig sig) { + this.owner = owner; + this.row = row; + this.name = name; + this.flags = flags; + this.rva = rva; + this.sig = sig; + } + + public boolean isStatic() { + return (flags & METHOD_STATIC) != 0; + } + + public boolean isVirtual() { + return (flags & METHOD_VIRTUAL) != 0; + } + + public boolean isAbstract() { + return (flags & METHOD_ABSTRACT) != 0; + } + + public boolean isNewSlot() { + return (flags & METHOD_NEW_SLOT) != 0; + } + + public boolean isPrivate() { + return (flags & METHOD_ACCESS_MASK) == METHOD_PRIVATE; + } + + public boolean hasBody() { + return rva != 0; + } + + public MethodBody body() { + if (body == null) { + body = new MethodBody(owner.assembly, rva); + } + return body; + } + + public int token() { + return (MetadataReader.METHOD_DEF << 24) | row; + } + + @Override + public String toString() { + return owner.fullName() + "::" + name + sig; + } + } + + /// An explicit override: `body` is this type's implementation of + /// `declaration`, whatever the two are called. + public static final class MethodImpl { + public final MethodRef body; + public final MethodRef declaration; + + MethodImpl(MethodRef body, MethodRef declaration) { + this.body = body; + this.declaration = declaration; + } + } + + public static final class MethodSig { + public final boolean hasThis; + public final int genericParamCount; + public final CilType returnType; + public final CilType[] params; + + MethodSig(boolean hasThis, int genericParamCount, CilType returnType, CilType[] params) { + this.hasThis = hasThis; + this.genericParamCount = genericParamCount; + this.returnType = returnType; + this.params = params; + } + + public boolean sameShape(MethodSig other) { + if (hasThis != other.hasThis || genericParamCount != other.genericParamCount + || params.length != other.params.length || !returnType.equals(other.returnType)) { + return false; + } + for (int i = 0; i < params.length; i++) { + if (!params[i].equals(other.params[i])) { + return false; + } + } + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < params.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(params[i]); + } + return sb.append(") : ").append(returnType).toString(); + } + } + + /// A method as an instruction names it: where it is declared, what it is + /// called and its signature exactly as declared, type variables included. + /// The instantiation, when there is one, is carried beside the signature + /// and never substituted into it, because the erased JVM descriptor is + /// derived from the declaration while boxing decisions need the arguments. + public static final class MethodRef { + public final CilType declaringType; + public final String name; + public final MethodSig sig; + public final CilType[] methodArgs; + /// The definition, when the reference is to this assembly's own method. + public final MethodDef def; + + MethodRef(CilType declaringType, String name, MethodSig sig, CilType[] methodArgs, MethodDef def) { + this.declaringType = declaringType; + this.name = name; + this.sig = sig; + this.methodArgs = methodArgs; + this.def = def; + } + + public CilType[] typeArgs() { + return declaringType.kind == CilType.Kind.GENERICINST ? declaringType.args : null; + } + + @Override + public String toString() { + return declaringType + "::" + name + sig; + } + } + + public static final class FieldRef { + public final CilType declaringType; + public final String name; + public final CilType type; + public final FieldDef def; + + FieldRef(CilType declaringType, String name, CilType type, FieldDef def) { + this.declaringType = declaringType; + this.name = name; + this.type = type; + this.def = def; + } + + @Override + public String toString() { + return declaringType + "::" + name; + } + } + + private final MetadataReader md; + private final String name; + private Universe universe; + private final List types = new ArrayList(); + private final Map typesByName = new HashMap(); + private FieldDef[] fieldsByRow; + private MethodDef[] methodsByRow; + private final Map tokenCache = new HashMap(); + private Map> attributes; + private PortablePdb debug; + + public CilAssembly(byte[] data, String source) { + md = new MetadataReader(data, source); + if (md.rowCount(MetadataReader.ASSEMBLY) > 0) { + name = md.string(md.cell(MetadataReader.ASSEMBLY, 1, 7)); + } else { + name = md.string(md.cell(MetadataReader.MODULE, 1, 1)); + } + } + + public MetadataReader metadata() { + return md; + } + + /// Gives the assembly the portable PDB the compiler wrote beside it, + /// so that [#sourceLocation(MethodDef, int)] has something to answer + /// with. + public void debugInformation(PortablePdb pdb) { + debug = pdb; + } + + /// Where in the C# source an instruction of a method came from, as + /// `File.cs:line`, or null when the assembly came without a PDB or the + /// PDB does not say. + public String sourceLocation(MethodDef method, int ilOffset) { + return debug == null ? null : debug.location(method.row, ilOffset); + } + + public String source() { + return md.source(); + } + + public String name() { + return name; + } + + public List types() { + return Collections.unmodifiableList(types); + } + + public TypeDef type(String fullName) { + return typesByName.get(fullName); + } + + /// Reads the types. Split from construction because signatures mention + /// types of other assemblies, which have to be registered first. + void load(Universe owner) { + universe = owner; + int typeCount = md.rowCount(MetadataReader.TYPE_DEF); + for (int row = 1; row <= typeCount; row++) { + types.add(new TypeDef(this, row, md.string(md.cell(MetadataReader.TYPE_DEF, row, 2)), + md.string(md.cell(MetadataReader.TYPE_DEF, row, 1)), md.cell(MetadataReader.TYPE_DEF, row, 0))); + } + for (int row = 1; row <= md.rowCount(MetadataReader.NESTED_CLASS); row++) { + typeAt(md.cell(MetadataReader.NESTED_CLASS, row, 0)).enclosing = + typeAt(md.cell(MetadataReader.NESTED_CLASS, row, 1)); + } + for (TypeDef t : types) { + typesByName.put(t.fullName(), t); + } + } + + /// Reads what every type derives from. Whether a type is a value type is + /// read off its base, and other assemblies ask that question while they + /// link, so the bases of all assemblies are in place before any links. + void linkBases() { + for (TypeDef t : types) { + int extendsValue = md.cell(MetadataReader.TYPE_DEF, t.row, 3); + int extendsToken = md.codedToken(MetadataReader.TYPE_DEF_OR_REF, extendsValue); + if (MetadataReader.tokenRow(extendsToken) != 0) { + t.baseType = baseTypeFromToken(extendsToken); + } + } + } + + /// Reads everything that needs other assemblies' type names to be known. + void link() { + for (int row = 1; row <= md.rowCount(MetadataReader.GENERIC_PARAM); row++) { + int owner = md.codedToken(MetadataReader.TYPE_OR_METHOD_DEF, md.cell(MetadataReader.GENERIC_PARAM, row, 2)); + if (MetadataReader.tokenTable(owner) == MetadataReader.TYPE_DEF) { + typeAt(MetadataReader.tokenRow(owner)).genericParamCount++; + } + } + fieldsByRow = new FieldDef[md.rowCount(MetadataReader.FIELD) + 1]; + methodsByRow = new MethodDef[md.rowCount(MetadataReader.METHOD_DEF) + 1]; + for (TypeDef t : types) { + int fieldEnd = md.listEnd(MetadataReader.TYPE_DEF, t.row, 4, MetadataReader.FIELD); + for (int f = md.cell(MetadataReader.TYPE_DEF, t.row, 4); f < fieldEnd; f++) { + int[] blob = md.blob(md.cell(MetadataReader.FIELD, f, 2)); + SignatureParser parser = new SignatureParser(this, blob[0]); + FieldDef field = new FieldDef(t, f, md.string(md.cell(MetadataReader.FIELD, f, 1)), + md.cell(MetadataReader.FIELD, f, 0), parser.fieldSig()); + t.fields.add(field); + fieldsByRow[f] = field; + } + int methodEnd = md.listEnd(MetadataReader.TYPE_DEF, t.row, 5, MetadataReader.METHOD_DEF); + for (int m = md.cell(MetadataReader.TYPE_DEF, t.row, 5); m < methodEnd; m++) { + int[] blob = md.blob(md.cell(MetadataReader.METHOD_DEF, m, 4)); + MethodDef method = new MethodDef(t, m, md.string(md.cell(MetadataReader.METHOD_DEF, m, 3)), + md.cell(MetadataReader.METHOD_DEF, m, 2), + md.cell(MetadataReader.METHOD_DEF, m, 0), new SignatureParser(this, blob[0]).methodSig()); + t.methods.add(method); + methodsByRow[m] = method; + } + } + for (int row = 1; row <= md.rowCount(MetadataReader.GENERIC_PARAM); row++) { + int owner = md.codedToken(MetadataReader.TYPE_OR_METHOD_DEF, md.cell(MetadataReader.GENERIC_PARAM, row, 2)); + if (MetadataReader.tokenTable(owner) == MetadataReader.METHOD_DEF) { + methodsByRow[MetadataReader.tokenRow(owner)].genericParamCount++; + } + } + for (int row = 1; row <= md.rowCount(MetadataReader.INTERFACE_IMPL); row++) { + int token = md.codedToken(MetadataReader.TYPE_DEF_OR_REF, md.cell(MetadataReader.INTERFACE_IMPL, row, 1)); + typeAt(md.cell(MetadataReader.INTERFACE_IMPL, row, 0)).interfaces.add(typeFromToken(token)); + } + for (int row = 1; row <= md.rowCount(MetadataReader.FIELD_RVA); row++) { + fieldsByRow[md.cell(MetadataReader.FIELD_RVA, row, 1)].rva = md.cell(MetadataReader.FIELD_RVA, row, 0); + } + for (int row = 1; row <= md.rowCount(MetadataReader.CONSTANT); row++) { + int parent = md.codedToken(MetadataReader.HAS_CONSTANT, md.cell(MetadataReader.CONSTANT, row, 1)); + if (MetadataReader.tokenTable(parent) != MetadataReader.FIELD) { + continue; + } + int[] span = md.blob(md.cell(MetadataReader.CONSTANT, row, 2)); + int at = span[0]; + long value; + // The element type of the constant: the integers, signed and not. + switch (md.cell(MetadataReader.CONSTANT, row, 0) & 0xFF) { + case 0x02: + case 0x05: + value = md.u1(at); + break; + case 0x04: + value = (byte) md.u1(at); + break; + case 0x03: + case 0x07: + value = md.u2(at); + break; + case 0x06: + value = (short) md.u2(at); + break; + case 0x08: + value = md.i4(at); + break; + case 0x09: + value = md.i4(at) & 0xFFFFFFFFL; + break; + case 0x0A: + case 0x0B: + value = md.i8(at); + break; + default: + continue; + } + fieldsByRow[MetadataReader.tokenRow(parent)].constant = Long.valueOf(value); + } + for (int row = 1; row <= md.rowCount(MetadataReader.METHOD_SEMANTICS); row++) { + MethodDef method = methodsByRow[md.cell(MetadataReader.METHOD_SEMANTICS, row, 1)]; + int association = md.codedToken(MetadataReader.HAS_SEMANTICS, + md.cell(MetadataReader.METHOD_SEMANTICS, row, 2)); + method.semantics = md.cell(MetadataReader.METHOD_SEMANTICS, row, 0); + int table = MetadataReader.tokenTable(association); + method.associationName = md.string(md.cell(table, MetadataReader.tokenRow(association), 1)); + } + } + + /// Method overrides name methods of other types, whose own members must + /// have been read already, so this runs after every assembly is linked. + void linkOverrides() { + for (int row = 1; row <= md.rowCount(MetadataReader.METHOD_IMPL); row++) { + TypeDef owner = typeAt(md.cell(MetadataReader.METHOD_IMPL, row, 0)); + MethodRef body = methodRef(md.codedToken(MetadataReader.METHOD_DEF_OR_REF, + md.cell(MetadataReader.METHOD_IMPL, row, 1))); + MethodRef declaration = methodRef(md.codedToken(MetadataReader.METHOD_DEF_OR_REF, + md.cell(MetadataReader.METHOD_IMPL, row, 2))); + owner.methodImpls.add(new MethodImpl(body, declaration)); + } + } + + private TypeDef typeAt(int row) { + return types.get(row - 1); + } + + // The base type decides whether a type is a value type, so resolving it + // must not ask that question of the type being linked. A base is always a + // class, which is all the answer that is needed here. + private CilType baseTypeFromToken(int token) { + int table = MetadataReader.tokenTable(token); + if (table == MetadataReader.TYPE_SPEC) { + return typeFromToken(token); + } + String fullName = table == MetadataReader.TYPE_DEF ? typeAt(MetadataReader.tokenRow(token)).fullName() + : typeRefName(MetadataReader.tokenRow(token)); + CilType builtin = CilType.builtin(fullName); + return builtin != null ? builtin : CilType.named(fullName, false); + } + + String typeRefName(int row) { + int scope = md.codedToken(MetadataReader.RESOLUTION_SCOPE, md.cell(MetadataReader.TYPE_REF, row, 0)); + String typeName = md.string(md.cell(MetadataReader.TYPE_REF, row, 1)); + String namespace = md.string(md.cell(MetadataReader.TYPE_REF, row, 2)); + if (MetadataReader.tokenTable(scope) == MetadataReader.TYPE_REF && MetadataReader.tokenRow(scope) != 0) { + return typeRefName(MetadataReader.tokenRow(scope)) + "/" + typeName; + } + return namespace.length() == 0 ? typeName : namespace + "." + typeName; + } + + /// Full name of the type a TypeDef or TypeRef token names. + String typeNameFromToken(int token) { + int table = MetadataReader.tokenTable(token); + if (table == MetadataReader.TYPE_DEF) { + return typeAt(MetadataReader.tokenRow(token)).fullName(); + } + if (table == MetadataReader.TYPE_REF) { + return typeRefName(MetadataReader.tokenRow(token)); + } + throw new CilFormatException(source() + ": token 0x" + Integer.toHexString(token) + " does not name a type"); + } + + /// The type a TypeDef, TypeRef or TypeSpec token names. + public CilType typeFromToken(int token) { + Integer key = Integer.valueOf(token); + Object cached = tokenCache.get(key); + if (cached != null) { + return (CilType) cached; + } + CilType result; + int table = MetadataReader.tokenTable(token); + if (table == MetadataReader.TYPE_SPEC) { + int[] blob = md.blob(md.cell(MetadataReader.TYPE_SPEC, MetadataReader.tokenRow(token), 0)); + result = new SignatureParser(this, blob[0]).type(); + } else { + String fullName = typeNameFromToken(token); + result = CilType.builtin(fullName); + if (result == null) { + result = CilType.named(fullName, universe.require(fullName, source()).isValueType()); + } + } + tokenCache.put(key, result); + return result; + } + + CilType[] localsFromToken(int token) { + int[] blob = md.blob(md.cell(MetadataReader.STANDALONE_SIG, MetadataReader.tokenRow(token), 0)); + return new SignatureParser(this, blob[0]).locals(); + } + + /// The method a MethodDef, MemberRef or MethodSpec token names. + public MethodRef methodRef(int token) { + Integer key = Integer.valueOf(token); + Object cached = tokenCache.get(key); + if (cached != null) { + return (MethodRef) cached; + } + MethodRef result; + int table = MetadataReader.tokenTable(token); + int row = MetadataReader.tokenRow(token); + if (table == MetadataReader.METHOD_DEF) { + MethodDef def = methodsByRow[row]; + result = new MethodRef(def.owner.asType(), def.name, def.sig, null, def); + } else if (table == MetadataReader.MEMBER_REF) { + CilType parent = memberRefParent(row); + int[] blob = md.blob(md.cell(MetadataReader.MEMBER_REF, row, 2)); + result = new MethodRef(parent, md.string(md.cell(MetadataReader.MEMBER_REF, row, 1)), + new SignatureParser(this, blob[0]).methodSig(), null, null); + } else if (table == MetadataReader.METHOD_SPEC) { + MethodRef generic = methodRef(md.codedToken(MetadataReader.METHOD_DEF_OR_REF, + md.cell(MetadataReader.METHOD_SPEC, row, 0))); + int[] blob = md.blob(md.cell(MetadataReader.METHOD_SPEC, row, 1)); + result = new MethodRef(generic.declaringType, generic.name, generic.sig, + new SignatureParser(this, blob[0]).methodSpec(), generic.def); + } else { + throw new CilFormatException(source() + ": token 0x" + Integer.toHexString(token) + + " does not name a method"); + } + tokenCache.put(key, result); + return result; + } + + /// The field a Field or MemberRef token names. + public FieldRef fieldRef(int token) { + Integer key = Integer.valueOf(token); + Object cached = tokenCache.get(key); + if (cached != null) { + return (FieldRef) cached; + } + FieldRef result; + int table = MetadataReader.tokenTable(token); + int row = MetadataReader.tokenRow(token); + if (table == MetadataReader.FIELD) { + FieldDef def = fieldsByRow[row]; + result = new FieldRef(def.owner.asType(), def.name, def.type, def); + } else if (table == MetadataReader.MEMBER_REF) { + CilType parent = memberRefParent(row); + int[] blob = md.blob(md.cell(MetadataReader.MEMBER_REF, row, 2)); + result = new FieldRef(parent, md.string(md.cell(MetadataReader.MEMBER_REF, row, 1)), + new SignatureParser(this, blob[0]).fieldSig(), null); + } else { + throw new CilFormatException(source() + ": token 0x" + Integer.toHexString(token) + + " does not name a field"); + } + tokenCache.put(key, result); + return result; + } + + private CilType memberRefParent(int row) { + int parent = md.codedToken(MetadataReader.MEMBER_REF_PARENT, md.cell(MetadataReader.MEMBER_REF, row, 0)); + int table = MetadataReader.tokenTable(parent); + if (table == MetadataReader.TYPE_DEF || table == MetadataReader.TYPE_REF + || table == MetadataReader.TYPE_SPEC) { + return typeFromToken(parent); + } + throw new CilFormatException(source() + ": member reference " + row + + " has a parent this reader does not model (table 0x" + Integer.toHexString(table) + ")"); + } + + /// True when a `ldtoken` operand names a field (a static array + /// initialiser) and not a type or a method. + public boolean isFieldToken(int token) { + int table = MetadataReader.tokenTable(token); + if (table == MetadataReader.FIELD) { + return true; + } + if (table != MetadataReader.MEMBER_REF) { + return false; + } + int[] blob = md.blob(md.cell(MetadataReader.MEMBER_REF, MetadataReader.tokenRow(token), 2)); + return md.u1(blob[0]) == 0x06; + } + + /// The initial bytes of a field that has them (a compiler-generated + /// array initialiser). + public byte[] fieldData(FieldDef field, int length) { + if (field.rva == 0) { + throw new CilFormatException(source() + ": field " + field.name + " has no initial data"); + } + return md.bytes(md.rvaToOffset(field.rva), length); + } + + /// Full names of the attribute types applied to a type, field or method. + public List attributes(int token) { + if (attributes == null) { + attributes = new HashMap>(); + for (int row = 1; row <= md.rowCount(MetadataReader.CUSTOM_ATTRIBUTE); row++) { + int parent = md.codedToken(MetadataReader.HAS_CUSTOM_ATTRIBUTE, + md.cell(MetadataReader.CUSTOM_ATTRIBUTE, row, 0)); + int ctor = md.codedToken(MetadataReader.CUSTOM_ATTRIBUTE_TYPE, + md.cell(MetadataReader.CUSTOM_ATTRIBUTE, row, 1)); + String attributeType; + if (MetadataReader.tokenTable(ctor) == MetadataReader.METHOD_DEF) { + attributeType = methodsByRow[MetadataReader.tokenRow(ctor)].owner.fullName(); + } else { + int owner = md.codedToken(MetadataReader.MEMBER_REF_PARENT, + md.cell(MetadataReader.MEMBER_REF, MetadataReader.tokenRow(ctor), 0)); + int ownerTable = MetadataReader.tokenTable(owner); + if (ownerTable != MetadataReader.TYPE_DEF && ownerTable != MetadataReader.TYPE_REF) { + continue; + } + attributeType = typeNameFromToken(owner); + } + Integer key = Integer.valueOf(parent); + List list = attributes.get(key); + if (list == null) { + list = new ArrayList(); + attributes.put(key, list); + } + list.add(attributeType); + } + } + List found = attributes.get(Integer.valueOf(token)); + return found == null ? Collections.emptyList() : found; + } + + public MethodDef entryPoint() { + int token = md.entryPointToken(); + if (MetadataReader.tokenTable(token) != MetadataReader.METHOD_DEF || MetadataReader.tokenRow(token) == 0) { + return null; + } + return methodsByRow[MetadataReader.tokenRow(token)]; + } + + public String userString(int token) { + return md.userString(token); + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilFormatException.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilFormatException.java new file mode 100644 index 00000000000..67bae65b640 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilFormatException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +/// An assembly that cannot be read, or that uses something this reader does +/// not model. The message always names the file. +public final class CilFormatException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public CilFormatException(String message) { + super(message); + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilType.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilType.java new file mode 100644 index 00000000000..c79b0b7e7c7 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/CilType.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +import java.util.Arrays; + +/// A type as a signature spells it. Immutable, and compared structurally, so +/// two signatures read from different assemblies match when they mean the +/// same thing. +/// +/// A named type carries its full CIL name: namespace, a dot, the name, and +/// `/` between a nested type and the type enclosing it. Generic arity stays in +/// the name as the compiler wrote it (``List`1``). +public final class CilType { + public enum Kind { + VOID, BOOLEAN, CHAR, I1, U1, I2, U2, I4, U4, I8, U8, R4, R8, I, U, STRING, OBJECT, + CLASS, VALUETYPE, VAR, MVAR, SZARRAY, ARRAY, GENERICINST, BYREF, PTR, TYPEDBYREF, FNPTR + } + + // Above the constants on purpose: their constructor reads it, and a + // static declared below them is still null while they are built. + private static final CilType[] NO_ARGS = new CilType[0]; + + public static final CilType VOID = new CilType(Kind.VOID, null, null, null, 0); + public static final CilType BOOLEAN = new CilType(Kind.BOOLEAN, null, null, null, 0); + public static final CilType CHAR = new CilType(Kind.CHAR, null, null, null, 0); + public static final CilType I1 = new CilType(Kind.I1, null, null, null, 0); + public static final CilType U1 = new CilType(Kind.U1, null, null, null, 0); + public static final CilType I2 = new CilType(Kind.I2, null, null, null, 0); + public static final CilType U2 = new CilType(Kind.U2, null, null, null, 0); + public static final CilType I4 = new CilType(Kind.I4, null, null, null, 0); + public static final CilType U4 = new CilType(Kind.U4, null, null, null, 0); + public static final CilType I8 = new CilType(Kind.I8, null, null, null, 0); + public static final CilType U8 = new CilType(Kind.U8, null, null, null, 0); + public static final CilType R4 = new CilType(Kind.R4, null, null, null, 0); + public static final CilType R8 = new CilType(Kind.R8, null, null, null, 0); + public static final CilType I = new CilType(Kind.I, null, null, null, 0); + public static final CilType U = new CilType(Kind.U, null, null, null, 0); + public static final CilType STRING = new CilType(Kind.STRING, null, null, null, 0); + public static final CilType OBJECT = new CilType(Kind.OBJECT, null, null, null, 0); + public static final CilType TYPEDBYREF = new CilType(Kind.TYPEDBYREF, null, null, null, 0); + public static final CilType FNPTR = new CilType(Kind.FNPTR, null, null, null, 0); + + public final Kind kind; + /// Full name, for CLASS and VALUETYPE. + public final String name; + /// Element of an array, byref or pointer; the generic definition of a GENERICINST. + public final CilType element; + /// Type arguments of a GENERICINST. + public final CilType[] args; + /// Position of a VAR or MVAR; rank of an ARRAY. + public final int index; + + private CilType(Kind kind, String name, CilType element, CilType[] args, int index) { + this.kind = kind; + this.name = name; + this.element = element; + this.args = args == null ? NO_ARGS : args; + this.index = index; + } + + public static CilType named(String name, boolean valueType) { + return new CilType(valueType ? Kind.VALUETYPE : Kind.CLASS, name, null, null, 0); + } + + public static CilType var(int index) { + return new CilType(Kind.VAR, null, null, null, index); + } + + public static CilType mvar(int index) { + return new CilType(Kind.MVAR, null, null, null, index); + } + + public static CilType szArray(CilType element) { + return new CilType(Kind.SZARRAY, null, element, null, 1); + } + + public static CilType array(CilType element, int rank) { + return new CilType(Kind.ARRAY, null, element, null, rank); + } + + public static CilType byRef(CilType element) { + return new CilType(Kind.BYREF, null, element, null, 0); + } + + public static CilType pointer(CilType element) { + return new CilType(Kind.PTR, null, element, null, 0); + } + + public static CilType genericInst(CilType definition, CilType[] args) { + return new CilType(Kind.GENERICINST, null, definition, args, 0); + } + + /// The named type behind this one: itself for a class or value type, the + /// definition for an instantiation, null for everything else. + public CilType definition() { + if (kind == Kind.GENERICINST) { + return element; + } + return kind == Kind.CLASS || kind == Kind.VALUETYPE ? this : null; + } + + /// Full name of the named type behind this one, or the `System` name a + /// built-in stands for. Null for arrays, variables and pointers. + public String typeName() { + switch (kind) { + case CLASS: + case VALUETYPE: + return name; + case GENERICINST: + return element.name; + case BOOLEAN: + return "System.Boolean"; + case CHAR: + return "System.Char"; + case I1: + return "System.SByte"; + case U1: + return "System.Byte"; + case I2: + return "System.Int16"; + case U2: + return "System.UInt16"; + case I4: + return "System.Int32"; + case U4: + return "System.UInt32"; + case I8: + return "System.Int64"; + case U8: + return "System.UInt64"; + case R4: + return "System.Single"; + case R8: + return "System.Double"; + case I: + return "System.IntPtr"; + case U: + return "System.UIntPtr"; + case STRING: + return "System.String"; + case OBJECT: + return "System.Object"; + case VOID: + return "System.Void"; + default: + return null; + } + } + + /// The built-in a `System` name stands for, so that a reference to + /// `System.Int32` and the `int32` element type are one type. Null when + /// the name is not a built-in. + public static CilType builtin(String fullName) { + if (!fullName.startsWith("System.")) { + return null; + } + switch (fullName) { + case "System.Boolean": + return BOOLEAN; + case "System.Char": + return CHAR; + case "System.SByte": + return I1; + case "System.Byte": + return U1; + case "System.Int16": + return I2; + case "System.UInt16": + return U2; + case "System.Int32": + return I4; + case "System.UInt32": + return U4; + case "System.Int64": + return I8; + case "System.UInt64": + return U8; + case "System.Single": + return R4; + case "System.Double": + return R8; + case "System.IntPtr": + return I; + case "System.UIntPtr": + return U; + case "System.String": + return STRING; + case "System.Object": + return OBJECT; + case "System.Void": + return VOID; + default: + return null; + } + } + + public boolean isPrimitive() { + switch (kind) { + case BOOLEAN: + case CHAR: + case I1: + case U1: + case I2: + case U2: + case I4: + case U4: + case I8: + case U8: + case R4: + case R8: + case I: + case U: + return true; + default: + return false; + } + } + + /// Replaces type variables with the arguments of an instantiation. Either + /// array may be null when that level is not instantiated. + public CilType substitute(CilType[] typeArgs, CilType[] methodArgs) { + switch (kind) { + case VAR: + return typeArgs != null && index < typeArgs.length ? typeArgs[index] : this; + case MVAR: + return methodArgs != null && index < methodArgs.length ? methodArgs[index] : this; + case SZARRAY: + return szArray(element.substitute(typeArgs, methodArgs)); + case ARRAY: + return array(element.substitute(typeArgs, methodArgs), index); + case BYREF: + return byRef(element.substitute(typeArgs, methodArgs)); + case PTR: + return pointer(element.substitute(typeArgs, methodArgs)); + case GENERICINST: { + CilType[] out = new CilType[args.length]; + for (int i = 0; i < out.length; i++) { + out[i] = args[i].substitute(typeArgs, methodArgs); + } + return genericInst(element, out); + } + default: + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CilType)) { + return false; + } + CilType t = (CilType) o; + if (kind != t.kind || index != t.index) { + return false; + } + if (name == null ? t.name != null : !name.equals(t.name)) { + return false; + } + if (element == null ? t.element != null : !element.equals(t.element)) { + return false; + } + return Arrays.equals(args, t.args); + } + + @Override + public int hashCode() { + int h = kind.ordinal() * 31 + index; + if (name != null) { + h = h * 31 + name.hashCode(); + } + if (element != null) { + h = h * 31 + element.hashCode(); + } + return h * 31 + Arrays.hashCode(args); + } + + @Override + public String toString() { + switch (kind) { + case CLASS: + return name; + case VALUETYPE: + return "valuetype " + name; + case VAR: + return "!" + index; + case MVAR: + return "!!" + index; + case SZARRAY: + return element + "[]"; + case ARRAY: + return element + "[rank " + index + "]"; + case BYREF: + return element + "&"; + case PTR: + return element + "*"; + case GENERICINST: { + StringBuilder sb = new StringBuilder(element.toString()).append('<'); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(','); + } + sb.append(args[i]); + } + return sb.append('>').toString(); + } + default: + return kind.name().toLowerCase(java.util.Locale.ROOT); + } + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/MetadataReader.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/MetadataReader.java new file mode 100644 index 00000000000..3adfea4b29d --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/MetadataReader.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +import java.nio.charset.StandardCharsets; + +/// The physical layer of a .NET assembly: the PE container, the metadata +/// streams and the metadata tables, as ECMA-335 partition II describes them. +/// +/// Nothing here interprets a row. A table is addressed by its number, a row by +/// its 1-based index and a column by its position, and the answer is the raw +/// integer stored there. [CilAssembly] turns those into types and methods. +/// +/// The schema below has to list every table the standard defines, used or not: +/// rows are packed back to back with no per-table offset, so the position of a +/// table is only known once the width of every table before it is. +public final class MetadataReader { + public static final int MODULE = 0x00; + public static final int TYPE_REF = 0x01; + public static final int TYPE_DEF = 0x02; + public static final int FIELD = 0x04; + public static final int METHOD_DEF = 0x06; + public static final int PARAM = 0x08; + public static final int INTERFACE_IMPL = 0x09; + public static final int MEMBER_REF = 0x0A; + public static final int CONSTANT = 0x0B; + public static final int CUSTOM_ATTRIBUTE = 0x0C; + public static final int CLASS_LAYOUT = 0x0F; + public static final int STANDALONE_SIG = 0x11; + public static final int EVENT_MAP = 0x12; + public static final int EVENT = 0x14; + public static final int PROPERTY_MAP = 0x15; + public static final int PROPERTY = 0x17; + public static final int METHOD_SEMANTICS = 0x18; + public static final int METHOD_IMPL = 0x19; + public static final int MODULE_REF = 0x1A; + public static final int TYPE_SPEC = 0x1B; + public static final int FIELD_RVA = 0x1D; + public static final int ASSEMBLY = 0x20; + public static final int ASSEMBLY_REF = 0x23; + public static final int FILE = 0x26; + public static final int EXPORTED_TYPE = 0x27; + public static final int NESTED_CLASS = 0x29; + public static final int GENERIC_PARAM = 0x2A; + public static final int METHOD_SPEC = 0x2B; + public static final int GENERIC_PARAM_CONSTRAINT = 0x2C; + + private static final int TABLE_COUNT = 0x2D; + + // Column kinds. A positive value is a fixed width in bytes; the heap + // indexes are negative; a simple table index is T + table and a coded + // index is C + its position in CODED. + private static final int STR = -1; + private static final int GUID = -2; + private static final int BLOB = -3; + private static final int T = 100; + private static final int C = 200; + + public static final int TYPE_DEF_OR_REF = 0; + public static final int HAS_CONSTANT = 1; + public static final int HAS_CUSTOM_ATTRIBUTE = 2; + public static final int HAS_FIELD_MARSHAL = 3; + public static final int HAS_DECL_SECURITY = 4; + public static final int MEMBER_REF_PARENT = 5; + public static final int HAS_SEMANTICS = 6; + public static final int METHOD_DEF_OR_REF = 7; + public static final int MEMBER_FORWARDED = 8; + public static final int IMPLEMENTATION = 9; + public static final int CUSTOM_ATTRIBUTE_TYPE = 10; + public static final int RESOLUTION_SCOPE = 11; + public static final int TYPE_OR_METHOD_DEF = 12; + + // The tables each coded index can point into, in tag order. -1 is a tag + // value the standard leaves unused. + private static final int[][] CODED = { + {TYPE_DEF, TYPE_REF, TYPE_SPEC}, + {FIELD, PARAM, PROPERTY}, + {METHOD_DEF, FIELD, TYPE_REF, TYPE_DEF, PARAM, INTERFACE_IMPL, MEMBER_REF, MODULE, 0x0E, PROPERTY, EVENT, + STANDALONE_SIG, MODULE_REF, TYPE_SPEC, ASSEMBLY, ASSEMBLY_REF, FILE, EXPORTED_TYPE, 0x28, GENERIC_PARAM, + GENERIC_PARAM_CONSTRAINT, METHOD_SPEC}, + {FIELD, PARAM}, + {TYPE_DEF, METHOD_DEF, ASSEMBLY}, + {TYPE_DEF, TYPE_REF, MODULE_REF, METHOD_DEF, TYPE_SPEC}, + {EVENT, PROPERTY}, + {METHOD_DEF, MEMBER_REF}, + {FIELD, METHOD_DEF}, + {FILE, ASSEMBLY_REF, EXPORTED_TYPE}, + {-1, -1, METHOD_DEF, MEMBER_REF, -1}, + {MODULE, MODULE_REF, ASSEMBLY_REF, TYPE_REF}, + {TYPE_DEF, METHOD_DEF}, + }; + + private static final int[][] COLUMNS = new int[TABLE_COUNT][]; + + static { + COLUMNS[0x00] = new int[] {2, STR, GUID, GUID, GUID}; + COLUMNS[0x01] = new int[] {C + RESOLUTION_SCOPE, STR, STR}; + COLUMNS[0x02] = new int[] {4, STR, STR, C + TYPE_DEF_OR_REF, T + FIELD, T + METHOD_DEF}; + COLUMNS[0x03] = new int[] {T + FIELD}; + COLUMNS[0x04] = new int[] {2, STR, BLOB}; + COLUMNS[0x05] = new int[] {T + METHOD_DEF}; + COLUMNS[0x06] = new int[] {4, 2, 2, STR, BLOB, T + PARAM}; + COLUMNS[0x07] = new int[] {T + PARAM}; + COLUMNS[0x08] = new int[] {2, 2, STR}; + COLUMNS[0x09] = new int[] {T + TYPE_DEF, C + TYPE_DEF_OR_REF}; + COLUMNS[0x0A] = new int[] {C + MEMBER_REF_PARENT, STR, BLOB}; + COLUMNS[0x0B] = new int[] {2, C + HAS_CONSTANT, BLOB}; + COLUMNS[0x0C] = new int[] {C + HAS_CUSTOM_ATTRIBUTE, C + CUSTOM_ATTRIBUTE_TYPE, BLOB}; + COLUMNS[0x0D] = new int[] {C + HAS_FIELD_MARSHAL, BLOB}; + COLUMNS[0x0E] = new int[] {2, C + HAS_DECL_SECURITY, BLOB}; + COLUMNS[0x0F] = new int[] {2, 4, T + TYPE_DEF}; + COLUMNS[0x10] = new int[] {4, T + FIELD}; + COLUMNS[0x11] = new int[] {BLOB}; + COLUMNS[0x12] = new int[] {T + TYPE_DEF, T + EVENT}; + COLUMNS[0x13] = new int[] {T + EVENT}; + COLUMNS[0x14] = new int[] {2, STR, C + TYPE_DEF_OR_REF}; + COLUMNS[0x15] = new int[] {T + TYPE_DEF, T + PROPERTY}; + COLUMNS[0x16] = new int[] {T + PROPERTY}; + COLUMNS[0x17] = new int[] {2, STR, BLOB}; + COLUMNS[0x18] = new int[] {2, T + METHOD_DEF, C + HAS_SEMANTICS}; + COLUMNS[0x19] = new int[] {T + TYPE_DEF, C + METHOD_DEF_OR_REF, C + METHOD_DEF_OR_REF}; + COLUMNS[0x1A] = new int[] {STR}; + COLUMNS[0x1B] = new int[] {BLOB}; + COLUMNS[0x1C] = new int[] {2, C + MEMBER_FORWARDED, STR, T + MODULE_REF}; + COLUMNS[0x1D] = new int[] {4, T + FIELD}; + COLUMNS[0x1E] = new int[] {4, 4}; + COLUMNS[0x1F] = new int[] {4}; + COLUMNS[0x20] = new int[] {4, 2, 2, 2, 2, 4, BLOB, STR, STR}; + COLUMNS[0x21] = new int[] {4}; + COLUMNS[0x22] = new int[] {4, 4, 4}; + COLUMNS[0x23] = new int[] {2, 2, 2, 2, 4, BLOB, STR, STR, BLOB}; + COLUMNS[0x24] = new int[] {4, T + ASSEMBLY_REF}; + COLUMNS[0x25] = new int[] {4, 4, 4, T + ASSEMBLY_REF}; + COLUMNS[0x26] = new int[] {4, STR, BLOB}; + COLUMNS[0x27] = new int[] {4, 4, STR, STR, C + IMPLEMENTATION}; + COLUMNS[0x28] = new int[] {4, 4, STR, C + IMPLEMENTATION}; + COLUMNS[0x29] = new int[] {T + TYPE_DEF, T + TYPE_DEF}; + COLUMNS[0x2A] = new int[] {2, 2, C + TYPE_OR_METHOD_DEF, STR}; + COLUMNS[0x2B] = new int[] {C + METHOD_DEF_OR_REF, BLOB}; + COLUMNS[0x2C] = new int[] {T + GENERIC_PARAM, C + TYPE_DEF_OR_REF}; + } + + private final byte[] data; + private final String source; + + private int[] sectionRva; + private int[] sectionSize; + private int[] sectionOffset; + + private int stringsOffset; + private int userStringsOffset; + private int blobOffset; + private boolean wideStrings; + private boolean wideGuids; + private boolean wideBlobs; + + private final int[] rowCounts = new int[TABLE_COUNT]; + private final int[] tableOffsets = new int[TABLE_COUNT]; + private final int[] rowSizes = new int[TABLE_COUNT]; + private final int[][] columnOffsets = new int[TABLE_COUNT][]; + private final int[][] columnSizes = new int[TABLE_COUNT][]; + + private int entryPointToken; + + public MetadataReader(byte[] data, String source) { + this.data = data; + this.source = source; + readPe(); + } + + public String source() { + return source; + } + + private CilFormatException bad(String what) { + return new CilFormatException(source + ": " + what); + } + + public int u1(int at) { + return data[at] & 0xFF; + } + + public int u2(int at) { + return (data[at] & 0xFF) | ((data[at + 1] & 0xFF) << 8); + } + + public int i4(int at) { + return (data[at] & 0xFF) | ((data[at + 1] & 0xFF) << 8) | ((data[at + 2] & 0xFF) << 16) + | ((data[at + 3] & 0xFF) << 24); + } + + public long i8(int at) { + return (i4(at) & 0xFFFFFFFFL) | ((long) i4(at + 4) << 32); + } + + public byte[] bytes(int at, int length) { + byte[] out = new byte[length]; + System.arraycopy(data, at, out, 0, length); + return out; + } + + private void readPe() { + if (data.length < 0x40 || u2(0) != 0x5A4D) { + throw bad("not a PE file"); + } + int pe = i4(0x3C); + if (pe <= 0 || pe + 24 > data.length || i4(pe) != 0x00004550) { + throw bad("missing PE signature"); + } + int coff = pe + 4; + int sections = u2(coff + 2); + int optionalSize = u2(coff + 16); + int optional = coff + 20; + int magic = u2(optional); + int directories; + if (magic == 0x10B) { + directories = optional + 96; + } else if (magic == 0x20B) { + directories = optional + 112; + } else { + throw bad("unknown optional header magic 0x" + Integer.toHexString(magic)); + } + int sectionTable = optional + optionalSize; + sectionRva = new int[sections]; + sectionSize = new int[sections]; + sectionOffset = new int[sections]; + for (int i = 0; i < sections; i++) { + int s = sectionTable + i * 40; + int virtualSize = i4(s + 8); + int rawSize = i4(s + 16); + sectionRva[i] = i4(s + 12); + sectionSize[i] = Math.max(virtualSize, rawSize); + sectionOffset[i] = i4(s + 20); + } + // Directory 14 is the CLI header; a native image has none. + int cliRva = i4(directories + 14 * 8); + if (cliRva == 0) { + throw bad("not a managed assembly (no CLI header)"); + } + int cli = rvaToOffset(cliRva); + int metadata = rvaToOffset(i4(cli + 8)); + entryPointToken = i4(cli + 20); + readMetadataRoot(metadata); + } + + /// Maps a relative virtual address, which is how the metadata names a + /// method body or a field's initial data, to a position in the file. + public int rvaToOffset(int rva) { + for (int i = 0; i < sectionRva.length; i++) { + if (rva >= sectionRva[i] && rva < sectionRva[i] + sectionSize[i]) { + return rva - sectionRva[i] + sectionOffset[i]; + } + } + throw bad("RVA 0x" + Integer.toHexString(rva) + " is outside every section"); + } + + private void readMetadataRoot(int root) { + if (i4(root) != 0x424A5342) { + throw bad("bad metadata signature"); + } + int versionLength = i4(root + 12); + int at = root + 16 + versionLength; + int streams = u2(at + 2); + at += 4; + int tables = -1; + for (int i = 0; i < streams; i++) { + int offset = i4(at); + at += 8; + int nameStart = at; + while (data[at] != 0) { + at++; + } + String name = new String(data, nameStart, at - nameStart, StandardCharsets.US_ASCII); + // The name is NUL terminated and then padded to a 4 byte boundary. + at = nameStart + ((at - nameStart + 4) & ~3); + if ("#~".equals(name) || "#-".equals(name)) { + tables = root + offset; + } else if ("#Strings".equals(name)) { + stringsOffset = root + offset; + } else if ("#US".equals(name)) { + userStringsOffset = root + offset; + } else if ("#Blob".equals(name)) { + blobOffset = root + offset; + } + } + if (tables < 0) { + throw bad("no metadata table stream"); + } + readTables(tables); + } + + private void readTables(int at) { + int heapSizes = u1(at + 6); + wideStrings = (heapSizes & 1) != 0; + wideGuids = (heapSizes & 2) != 0; + wideBlobs = (heapSizes & 4) != 0; + long valid = i8(at + 8); + int p = at + 24; + for (int t = 0; t < 64; t++) { + if ((valid & (1L << t)) == 0) { + continue; + } + if (t >= TABLE_COUNT) { + throw bad("unknown metadata table 0x" + Integer.toHexString(t)); + } + rowCounts[t] = i4(p); + p += 4; + } + for (int t = 0; t < TABLE_COUNT; t++) { + int[] kinds = COLUMNS[t]; + int[] offsets = new int[kinds.length]; + int[] sizes = new int[kinds.length]; + int width = 0; + for (int c = 0; c < kinds.length; c++) { + offsets[c] = width; + sizes[c] = columnSize(kinds[c]); + width += sizes[c]; + } + columnOffsets[t] = offsets; + columnSizes[t] = sizes; + rowSizes[t] = width; + tableOffsets[t] = p; + p += width * rowCounts[t]; + } + } + + private int columnSize(int kind) { + if (kind > 0 && kind < T) { + return kind; + } + if (kind == STR) { + return wideStrings ? 4 : 2; + } + if (kind == GUID) { + return wideGuids ? 4 : 2; + } + if (kind == BLOB) { + return wideBlobs ? 4 : 2; + } + if (kind >= C) { + int[] targets = CODED[kind - C]; + int max = 0; + for (int target : targets) { + if (target >= 0) { + max = Math.max(max, rowCounts[target]); + } + } + return max < (1 << (16 - tagBits(targets.length))) ? 2 : 4; + } + return rowCounts[kind - T] < 65536 ? 2 : 4; + } + + private static int tagBits(int choices) { + int bits = 0; + while ((1 << bits) < choices) { + bits++; + } + return bits; + } + + public int rowCount(int table) { + return rowCounts[table]; + } + + /// The raw value of one cell. `row` is 1-based, as every reference in the + /// metadata is; row 0 means "none" and is never stored. + public int cell(int table, int row, int column) { + if (row < 1 || row > rowCounts[table]) { + throw bad("row " + row + " is outside table 0x" + Integer.toHexString(table) + " (" + rowCounts[table] + + " rows)"); + } + int at = tableOffsets[table] + (row - 1) * rowSizes[table] + columnOffsets[table][column]; + return columnSizes[table][column] == 2 ? u2(at) : i4(at); + } + + /// Decodes a coded index into a token: the table number in the top byte + /// and the row below it, which is also how IL operands name a row. + public int codedToken(int coded, int value) { + int[] targets = CODED[coded]; + int bits = tagBits(targets.length); + int tag = value & ((1 << bits) - 1); + int row = value >>> bits; + if (tag >= targets.length || targets[tag] < 0) { + throw bad("coded index tag " + tag + " is not defined for coded index " + coded); + } + return (targets[tag] << 24) | row; + } + + public static int tokenTable(int token) { + return token >>> 24; + } + + public static int tokenRow(int token) { + return token & 0xFFFFFF; + } + + /// The first row past a row's member list. A list column (the fields or + /// methods of a type) holds only where the run starts; it ends where the + /// next row's run starts, or at the end of the member table. + public int listEnd(int table, int row, int column, int memberTable) { + if (row < rowCounts[table]) { + return cell(table, row + 1, column); + } + return rowCounts[memberTable] + 1; + } + + public String string(int index) { + int start = stringsOffset + index; + int end = start; + while (data[end] != 0) { + end++; + } + return new String(data, start, end - start, StandardCharsets.UTF_8); + } + + /// Position and length of a blob, as `{offset, length}`. + public int[] blob(int index) { + return compressedLengthPrefixed(blobOffset + index); + } + + private int[] compressedLengthPrefixed(int at) { + int first = u1(at); + if ((first & 0x80) == 0) { + return new int[] {at + 1, first}; + } + if ((first & 0xC0) == 0x80) { + return new int[] {at + 2, ((first & 0x3F) << 8) | u1(at + 1)}; + } + return new int[] {at + 4, ((first & 0x1F) << 24) | (u1(at + 1) << 16) | (u1(at + 2) << 8) | u1(at + 3)}; + } + + /// The string an `ldstr` token names. User strings are UTF-16LE with one + /// trailing flag byte that is not part of the text. + public String userString(int token) { + int[] span = compressedLengthPrefixed(userStringsOffset + tokenRow(token)); + int chars = span[1] / 2; + char[] out = new char[chars]; + for (int i = 0; i < chars; i++) { + out[i] = (char) u2(span[0] + i * 2); + } + return new String(out); + } + + public int entryPointToken() { + return entryPointToken; + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/MethodBody.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/MethodBody.java new file mode 100644 index 00000000000..24380fb7d55 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/MethodBody.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/// The decoded body of one method: its locals, its instructions and its +/// exception clauses. +/// +/// Instructions are normalised while they are read so that a consumer sees one +/// spelling of each operation. The compact forms a compiler picks to save +/// bytes -- `ldarg.0`, `ldc.i4.s`, `br.s` -- come out as the general form with +/// the operand filled in, and a branch operand is the absolute offset of its +/// target, never the relative distance the file stores. +public final class MethodBody { + + /// One instruction. Which operand field is meaningful follows from the + /// operation: a token, an index, a 32-bit constant or a branch target is + /// in [#operand]. + public static final class Instr { + public final int offset; + /// Offset of the instruction that follows this one in the stream. + public final int next; + public final String op; + public final int operand; + public final long longOperand; + public final double doubleOperand; + public final int[] targets; + + Instr(int offset, int next, String op, int operand, long longOperand, double doubleOperand, int[] targets) { + this.offset = offset; + this.next = next; + this.op = op; + this.operand = operand; + this.longOperand = longOperand; + this.doubleOperand = doubleOperand; + this.targets = targets; + } + + @Override + public String toString() { + return "IL_" + Integer.toHexString(offset) + ": " + op; + } + } + + /// One row of the exception table. The ranges are half open: + /// `[tryStart, tryEnd)` and `[handlerStart, handlerEnd)`. + public static final class Clause { + public static final int CATCH = 0; + public static final int FILTER = 1; + public static final int FINALLY = 2; + public static final int FAULT = 4; + + public final int flags; + public final int tryStart; + public final int tryEnd; + public final int handlerStart; + public final int handlerEnd; + /// Token of the caught type for a catch clause; filter offset for a filter. + public final int classToken; + + Clause(int flags, int tryStart, int tryLength, int handlerStart, int handlerLength, int classToken) { + this.flags = flags; + this.tryStart = tryStart; + this.tryEnd = tryStart + tryLength; + this.handlerStart = handlerStart; + this.handlerEnd = handlerStart + handlerLength; + this.classToken = classToken; + } + } + + private static final String ONE_BYTE = + "00 nop n;01 break n;02 ldarg.0 n;03 ldarg.1 n;04 ldarg.2 n;05 ldarg.3 n;06 ldloc.0 n;07 ldloc.1 n;" + + "08 ldloc.2 n;09 ldloc.3 n;0A stloc.0 n;0B stloc.1 n;0C stloc.2 n;0D stloc.3 n;0E ldarg.s v1;" + + "0F ldarga.s v1;10 starg.s v1;11 ldloc.s v1;12 ldloca.s v1;13 stloc.s v1;14 ldnull n;15 ldc.i4.m1 n;" + + "16 ldc.i4.0 n;17 ldc.i4.1 n;18 ldc.i4.2 n;19 ldc.i4.3 n;1A ldc.i4.4 n;1B ldc.i4.5 n;1C ldc.i4.6 n;" + + "1D ldc.i4.7 n;1E ldc.i4.8 n;1F ldc.i4.s c1;20 ldc.i4 i4;21 ldc.i8 i8;22 ldc.r4 r4;23 ldc.r8 r8;" + + "25 dup n;26 pop n;27 jmp t;28 call t;29 calli t;2A ret n;2B br.s b1;2C brfalse.s b1;2D brtrue.s b1;" + + "2E beq.s b1;2F bge.s b1;30 bgt.s b1;31 ble.s b1;32 blt.s b1;33 bne.un.s b1;34 bge.un.s b1;" + + "35 bgt.un.s b1;36 ble.un.s b1;37 blt.un.s b1;38 br b4;39 brfalse b4;3A brtrue b4;3B beq b4;3C bge b4;" + + "3D bgt b4;3E ble b4;3F blt b4;40 bne.un b4;41 bge.un b4;42 bgt.un b4;43 ble.un b4;44 blt.un b4;" + + "45 switch s;46 ldind.i1 n;47 ldind.u1 n;48 ldind.i2 n;49 ldind.u2 n;4A ldind.i4 n;4B ldind.u4 n;" + + "4C ldind.i8 n;4D ldind.i n;4E ldind.r4 n;4F ldind.r8 n;50 ldind.ref n;51 stind.ref n;52 stind.i1 n;" + + "53 stind.i2 n;54 stind.i4 n;55 stind.i8 n;56 stind.r4 n;57 stind.r8 n;58 add n;59 sub n;5A mul n;" + + "5B div n;5C div.un n;5D rem n;5E rem.un n;5F and n;60 or n;61 xor n;62 shl n;63 shr n;64 shr.un n;" + + "65 neg n;66 not n;67 conv.i1 n;68 conv.i2 n;69 conv.i4 n;6A conv.i8 n;6B conv.r4 n;6C conv.r8 n;" + + "6D conv.u4 n;6E conv.u8 n;6F callvirt t;70 cpobj t;71 ldobj t;72 ldstr t;73 newobj t;74 castclass t;" + + "75 isinst t;76 conv.r.un n;79 unbox t;7A throw n;7B ldfld t;7C ldflda t;7D stfld t;7E ldsfld t;" + + "7F ldsflda t;80 stsfld t;81 stobj t;82 conv.ovf.i1.un n;83 conv.ovf.i2.un n;84 conv.ovf.i4.un n;" + + "85 conv.ovf.i8.un n;86 conv.ovf.u1.un n;87 conv.ovf.u2.un n;88 conv.ovf.u4.un n;89 conv.ovf.u8.un n;" + + "8A conv.ovf.i.un n;8B conv.ovf.u.un n;8C box t;8D newarr t;8E ldlen n;8F ldelema t;90 ldelem.i1 n;" + + "91 ldelem.u1 n;92 ldelem.i2 n;93 ldelem.u2 n;94 ldelem.i4 n;95 ldelem.u4 n;96 ldelem.i8 n;" + + "97 ldelem.i n;98 ldelem.r4 n;99 ldelem.r8 n;9A ldelem.ref n;9B stelem.i n;9C stelem.i1 n;" + + "9D stelem.i2 n;9E stelem.i4 n;9F stelem.i8 n;A0 stelem.r4 n;A1 stelem.r8 n;A2 stelem.ref n;" + + "A3 ldelem t;A4 stelem t;A5 unbox.any t;B3 conv.ovf.i1 n;B4 conv.ovf.u1 n;B5 conv.ovf.i2 n;" + + "B6 conv.ovf.u2 n;B7 conv.ovf.i4 n;B8 conv.ovf.u4 n;B9 conv.ovf.i8 n;BA conv.ovf.u8 n;C2 refanyval t;" + + "C3 ckfinite n;C6 mkrefany t;D0 ldtoken t;D1 conv.u2 n;D2 conv.u1 n;D3 conv.i n;D4 conv.ovf.i n;" + + "D5 conv.ovf.u n;D6 add.ovf n;D7 add.ovf.un n;D8 mul.ovf n;D9 mul.ovf.un n;DA sub.ovf n;" + + "DB sub.ovf.un n;DC endfinally n;DD leave b4;DE leave.s b1;DF stind.i n;E0 conv.u n"; + + private static final String TWO_BYTE = + "00 arglist n;01 ceq n;02 cgt n;03 cgt.un n;04 clt n;05 clt.un n;06 ldftn t;07 ldvirtftn t;09 ldarg v2;" + + "0A ldarga v2;0B starg v2;0C ldloc v2;0D ldloca v2;0E stloc v2;0F localloc n;11 endfilter n;" + + "12 unaligned. c1;13 volatile. n;14 tail. n;15 initobj t;16 constrained. t;17 cpblk n;18 initblk n;" + + "19 no. c1;1A rethrow n;1C sizeof t;1D refanytype n;1E readonly. n"; + + private static final String[][] ONE = parseTable(ONE_BYTE); + private static final String[][] TWO = parseTable(TWO_BYTE); + private static final Map CANONICAL = new HashMap(); + + private static String[][] parseTable(String spec) { + String[][] table = new String[256][]; + for (String entry : spec.split(";")) { + String[] parts = entry.split(" "); + table[Integer.parseInt(parts[0], 16)] = new String[] {parts[1], parts[2]}; + } + return table; + } + + /// Splits a compact opcode name into the general operation and the + /// operand the name implies: `ldloc.2` is `ldloc` 2, `br.s` is `br`. + private static Object[] canonical(String raw) { + Object[] cached = CANONICAL.get(raw); + if (cached != null) { + return cached; + } + String op = raw; + Integer implied = null; + if ("ldc.i4.m1".equals(raw)) { + op = "ldc.i4"; + implied = Integer.valueOf(-1); + } else if ("ldc.i4.s".equals(raw)) { + op = "ldc.i4"; + } else { + int dot = raw.lastIndexOf('.'); + String head = dot < 0 ? raw : raw.substring(0, dot); + String tail = dot < 0 ? "" : raw.substring(dot + 1); + boolean digit = tail.length() == 1 && tail.charAt(0) >= '0' && tail.charAt(0) <= '9'; + if (digit && ("ldarg".equals(head) || "ldloc".equals(head) || "stloc".equals(head) + || "ldc.i4".equals(head))) { + op = head; + implied = Integer.valueOf(tail.charAt(0) - '0'); + } else if ("s".equals(tail)) { + op = head; + } + } + Object[] out = new Object[] {op.intern(), implied}; + CANONICAL.put(raw, out); + return out; + } + + public final CilType[] locals; + public final List instructions = new ArrayList(); + public final List clauses = new ArrayList(); + private final Map indexByOffset = new HashMap(); + + MethodBody(CilAssembly assembly, int rva) { + MetadataReader md = assembly.metadata(); + int at = md.rvaToOffset(rva); + int first = md.u1(at); + int code; + int codeSize; + boolean moreSections = false; + if ((first & 3) == 2) { + // Tiny header: no locals, no clauses, at most 8 stack slots. + locals = new CilType[0]; + codeSize = first >> 2; + code = at + 1; + } else { + int flags = md.u2(at); + int headerSize = (flags >> 12) * 4; + codeSize = md.i4(at + 4); + int localsToken = md.i4(at + 8); + moreSections = (flags & 0x08) != 0; + locals = localsToken == 0 ? new CilType[0] : assembly.localsFromToken(localsToken); + code = at + headerSize; + } + decode(md, code, codeSize); + if (moreSections) { + readClauses(md, (code + codeSize + 3) & ~3); + } + } + + private void decode(MetadataReader md, int code, int codeSize) { + int p = 0; + while (p < codeSize) { + int start = p; + int b = md.u1(code + p++); + String[] def; + if (b == 0xFE) { + def = TWO[md.u1(code + p++)]; + } else { + def = ONE[b]; + } + if (def == null) { + throw new CilFormatException(md.source() + ": unknown opcode 0x" + Integer.toHexString(b) + + " at IL_" + Integer.toHexString(start)); + } + Object[] canon = canonical(def[0]); + String op = (String) canon[0]; + int operand = canon[1] == null ? 0 : ((Integer) canon[1]).intValue(); + long longOperand = 0; + double doubleOperand = 0; + int[] targets = null; + String kind = def[1]; + if ("v1".equals(kind)) { + operand = md.u1(code + p); + p += 1; + } else if ("v2".equals(kind)) { + operand = md.u2(code + p); + p += 2; + } else if ("c1".equals(kind)) { + operand = (byte) md.u1(code + p); + p += 1; + } else if ("i4".equals(kind) || "t".equals(kind)) { + operand = md.i4(code + p); + p += 4; + } else if ("i8".equals(kind)) { + longOperand = md.i8(code + p); + p += 8; + } else if ("r4".equals(kind)) { + doubleOperand = Float.intBitsToFloat(md.i4(code + p)); + p += 4; + } else if ("r8".equals(kind)) { + doubleOperand = Double.longBitsToDouble(md.i8(code + p)); + p += 8; + } else if ("b1".equals(kind)) { + int delta = (byte) md.u1(code + p); + p += 1; + operand = p + delta; + } else if ("b4".equals(kind)) { + int delta = md.i4(code + p); + p += 4; + operand = p + delta; + } else if ("s".equals(kind)) { + int count = md.i4(code + p); + p += 4; + targets = new int[count]; + int base = p + count * 4; + for (int i = 0; i < count; i++) { + targets[i] = base + md.i4(code + p); + p += 4; + } + } + indexByOffset.put(Integer.valueOf(start), Integer.valueOf(instructions.size())); + instructions.add(new Instr(start, p, op, operand, longOperand, doubleOperand, targets)); + } + } + + private void readClauses(MetadataReader md, int at) { + while (true) { + int kind = md.u1(at); + if ((kind & 0x01) == 0) { + throw new CilFormatException(md.source() + ": unknown method data section 0x" + + Integer.toHexString(kind)); + } + boolean fat = (kind & 0x40) != 0; + int size; + if (fat) { + size = md.u1(at + 1) | (md.u1(at + 2) << 8) | (md.u1(at + 3) << 16); + for (int c = at + 4; c + 24 <= at + size; c += 24) { + clauses.add(new Clause(md.i4(c), md.i4(c + 4), md.i4(c + 8), md.i4(c + 12), md.i4(c + 16), + md.i4(c + 20))); + } + } else { + size = md.u1(at + 1); + for (int c = at + 4; c + 12 <= at + size; c += 12) { + clauses.add(new Clause(md.u2(c), md.u2(c + 2), md.u1(c + 4), md.u2(c + 5), md.u1(c + 7), + md.i4(c + 8))); + } + } + if ((kind & 0x80) == 0) { + return; + } + at = (at + size + 3) & ~3; + } + } + + /// Position in [#instructions] of the instruction at an IL offset. + public int indexOf(int offset) { + Integer index = indexByOffset.get(Integer.valueOf(offset)); + if (index == null) { + throw new CilFormatException("no instruction starts at IL_" + Integer.toHexString(offset)); + } + return index.intValue(); + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/PortablePdb.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/PortablePdb.java new file mode 100644 index 00000000000..09ba93e9015 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/PortablePdb.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +import java.nio.charset.StandardCharsets; + +/// The part of a portable PDB that says which line of which source file an +/// instruction came from. +/// +/// A portable PDB is the file the C# compiler writes beside an assembly +/// under `portable`. It is laid out as an assembly's +/// own metadata is (ECMA-335, II.24): the same root, the same heaps and a +/// table stream -- holding tables of its own, numbered from 0x30, in place +/// of the type system's. Two of them are read here: +/// +/// - `Document` (0x30): a source file, its name a blob of parts. +/// - `MethodDebugInformation` (0x31): one row for each row of the +/// assembly's `MethodDef` table, in the same order, with the method's +/// *sequence points* -- the places a debugger can stop, each an IL offset +/// and the source range it stands for. +/// +/// Those are the first two tables of the stream and neither has a column +/// that refers to a later one, so nothing else needs to be understood to +/// find them. The format is public: "Portable PDB v1.0: Format +/// Specification", in the `dotnet/runtime` documentation. +/// +/// It is only ever a help. A file that is absent, or is not what this +/// expects, gives no locations and no error: the translation it would have +/// described is unchanged. +public final class PortablePdb { + private static final int DOCUMENT = 0x30; + private static final int METHOD_DEBUG_INFORMATION = 0x31; + /// The line a compiler gives a sequence point that stands for no source. + private static final int HIDDEN_LINE = 0xFEEFEE; + + private final byte[] data; + private int stringsEnd; + private int blobOffset; + private int blobEnd; + private boolean wideBlobs; + private int documents; + private int documentsAt; + private int documentRow; + private int methods; + private int methodsAt; + private int methodRow; + private String[] documentNames; + private int at; + + private PortablePdb(byte[] data) { + this.data = data; + } + + /// Reads a PDB, or answers null for bytes that are not one. + public static PortablePdb read(byte[] data) { + PortablePdb pdb = new PortablePdb(data); + try { + return pdb.readRoot() ? pdb : null; + } catch (IndexOutOfBoundsException e) { + // Truncated, or not laid out as expected: no locations. + return null; + } + } + + private int u1(int p) { + return data[p] & 0xFF; + } + + private int u2(int p) { + return (data[p] & 0xFF) | ((data[p + 1] & 0xFF) << 8); + } + + private int i4(int p) { + return (data[p] & 0xFF) | ((data[p + 1] & 0xFF) << 8) | ((data[p + 2] & 0xFF) << 16) + | ((data[p + 3] & 0xFF) << 24); + } + + private boolean readRoot() { + if (data.length < 32 || i4(0) != 0x424A5342) { + return false; + } + int p = 16 + i4(12); + int streams = u2(p + 2); + p += 4; + int tables = -1; + boolean pdbStream = false; + for (int i = 0; i < streams; i++) { + int offset = i4(p); + int size = i4(p + 4); + p += 8; + int nameStart = p; + while (data[p] != 0) { + p++; + } + String name = new String(data, nameStart, p - nameStart, StandardCharsets.US_ASCII); + p = nameStart + ((p - nameStart + 4) & ~3); + if ("#~".equals(name)) { + tables = offset; + } else if ("#Blob".equals(name)) { + blobOffset = offset; + blobEnd = offset + size; + } else if ("#Strings".equals(name)) { + stringsEnd = offset + size; + } else if ("#Pdb".equals(name)) { + pdbStream = true; + } + } + if (tables < 0 || !pdbStream || blobEnd > data.length || stringsEnd > data.length) { + return false; + } + int heapSizes = u1(tables + 6); + boolean wideGuids = (heapSizes & 2) != 0; + wideBlobs = (heapSizes & 4) != 0; + long valid = (i4(tables + 8) & 0xFFFFFFFFL) | ((long) i4(tables + 12) << 32); + // A stand-alone PDB carries the debug tables alone. One merged into + // an assembly's own metadata would need every earlier table's row + // size, which is the other reader's business. + if ((valid & ((1L << DOCUMENT) - 1)) != 0) { + return false; + } + p = tables + 24; + int present = 0; + for (int t = DOCUMENT; t < 64; t++) { + if ((valid & (1L << t)) == 0) { + continue; + } + if (t == DOCUMENT) { + documents = i4(p); + } else if (t == METHOD_DEBUG_INFORMATION) { + methods = i4(p); + } + p += 4; + present++; + } + if (present == 0) { + return false; + } + int blob = wideBlobs ? 4 : 2; + int guid = wideGuids ? 4 : 2; + documentRow = blob + guid + blob + guid; + documentsAt = p; + methodRow = (documents < 65536 ? 2 : 4) + blob; + methodsAt = documentsAt + documents * documentRow; + if (methodsAt + methods * methodRow > data.length) { + return false; + } + documentNames = new String[documents + 1]; + return true; + } + + private int index(int p, boolean wide) { + return wide ? i4(p) : u2(p); + } + + /// An unsigned compressed integer (ECMA-335, II.23.2) at [#at], which + /// moves past it. + private int compressed() { + int first = u1(at); + if ((first & 0x80) == 0) { + at += 1; + return first; + } + if ((first & 0xC0) == 0x80) { + int v = ((first & 0x3F) << 8) | u1(at + 1); + at += 2; + return v; + } + int v = ((first & 0x1F) << 24) | (u1(at + 1) << 16) | (u1(at + 2) << 8) | u1(at + 3); + at += 4; + return v; + } + + /// A signed compressed integer: the sign is rotated into the lowest + /// bit, and how many bits the rest has depends on how many bytes the + /// number took. + private int signedCompressed() { + int start = at; + int raw = compressed(); + int value = raw >>> 1; + if ((raw & 1) == 0) { + return value; + } + int width = at - start; + return value - (width == 1 ? 0x40 : width == 2 ? 0x2000 : 0x10000000); + } + + /// The name of a document: a separator, then for each part of the path + /// the index of a blob holding it. Only the last part is kept -- the + /// rest is a directory of the machine that compiled it. + private String documentName(int row) { + if (row < 1 || row > documents) { + return null; + } + if (documentNames[row] != null) { + return documentNames[row]; + } + int saved = at; + at = blobOffset + index(documentsAt + (row - 1) * documentRow, wideBlobs); + int length = compressed(); + int end = at + length; + int separator = length > 0 ? u1(at) : 0; + at++; + String last = ""; + while (at < end) { + int part = compressed(); + if (part == 0) { + continue; + } + int resume = at; + at = blobOffset + part; + int partLength = compressed(); + String text = new String(data, at, partLength, StandardCharsets.UTF_8); + at = resume; + if (text.length() > 0) { + last = text; + } + } + if (separator == 0) { + // No separator: the name is one part, a whole path. + int slash = Math.max(last.lastIndexOf('/'), last.lastIndexOf('\\')); + last = slash >= 0 ? last.substring(slash + 1) : last; + } + at = saved; + documentNames[row] = last; + return last; + } + + /// Where in the source the instruction at an offset of a method came + /// from, as `File.cs:line`, or null when the PDB does not say. + /// `methodRow` is the method's row in the assembly's `MethodDef` table. + /// + /// The answer is the last sequence point at or before the offset that + /// stands for source, since an instruction belongs to the statement + /// that began before it; failing that -- an offset inside the code a + /// compiler puts ahead of the first statement -- the first that does. + public String location(int methodDefRow, int ilOffset) { + try { + return find(methodDefRow, ilOffset); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + private String find(int methodDefRow, int ilOffset) { + if (methodDefRow < 1 || methodDefRow > methods) { + return null; + } + int row = methodsAt + (methodDefRow - 1) * methodRow; + int document = documents < 65536 ? u2(row) : i4(row); + int blob = index(row + (documents < 65536 ? 2 : 4), wideBlobs); + if (blob == 0) { + return null; + } + at = blobOffset + blob; + int length = compressed(); + int end = at + length; + // The header: the signature of the locals, then the document when + // the row itself names none because the method spans several. + compressed(); + if (document == 0) { + document = compressed(); + } + String best = null; + String first = null; + int offset = 0; + int line = 0; + int column = 0; + boolean firstRecord = true; + boolean firstVisible = true; + while (at < end) { + int deltaOffset = compressed(); + if (!firstRecord && deltaOffset == 0) { + // Not a point: the points that follow are in another file. + document = compressed(); + continue; + } + firstRecord = false; + offset += deltaOffset; + int deltaLines = compressed(); + int deltaColumns = deltaLines == 0 ? compressed() : signedCompressed(); + if (deltaLines == 0 && deltaColumns == 0) { + // A hidden point: code that stands for no source. + continue; + } + if (firstVisible) { + line = compressed(); + column = compressed(); + firstVisible = false; + } else { + line += signedCompressed(); + column += signedCompressed(); + } + if (line == HIDDEN_LINE) { + continue; + } + String here = documentName(document); + here = here == null ? null : here + ":" + line; + if (first == null) { + first = here; + } + if (offset <= ilOffset) { + best = here; + } else { + break; + } + } + return best != null ? best : first; + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/SignatureParser.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/SignatureParser.java new file mode 100644 index 00000000000..d7c350952c3 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/SignatureParser.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +/// Decodes the signature blobs of ECMA-335 II.23.2: field, method, local +/// variable and generic instantiation signatures, and the types inside them. +final class SignatureParser { + private final CilAssembly assembly; + private final MetadataReader md; + private int at; + + SignatureParser(CilAssembly assembly, int at) { + this.assembly = assembly; + this.md = assembly.metadata(); + this.at = at; + } + + private int compressed() { + int first = md.u1(at); + if ((first & 0x80) == 0) { + at += 1; + return first; + } + if ((first & 0xC0) == 0x80) { + int value = ((first & 0x3F) << 8) | md.u1(at + 1); + at += 2; + return value; + } + int value = ((first & 0x1F) << 24) | (md.u1(at + 1) << 16) | (md.u1(at + 2) << 8) | md.u1(at + 3); + at += 4; + return value; + } + + private int typeDefOrRefToken() { + return md.codedToken(MetadataReader.TYPE_DEF_OR_REF, compressed()); + } + + private CilFormatException bad(String what) { + return new CilFormatException(md.source() + ": " + what); + } + + // A signature states outright whether a named type is a class or a value + // type, so nothing has to be resolved to read one. + private CilType named(boolean valueType) { + int token = typeDefOrRefToken(); + if (MetadataReader.tokenTable(token) == MetadataReader.TYPE_SPEC) { + return assembly.typeFromToken(token); + } + String fullName = assembly.typeNameFromToken(token); + CilType builtin = CilType.builtin(fullName); + return builtin != null ? builtin : CilType.named(fullName, valueType); + } + + CilType type() { + while (true) { + int element = md.u1(at++); + switch (element) { + case 0x01: + return CilType.VOID; + case 0x02: + return CilType.BOOLEAN; + case 0x03: + return CilType.CHAR; + case 0x04: + return CilType.I1; + case 0x05: + return CilType.U1; + case 0x06: + return CilType.I2; + case 0x07: + return CilType.U2; + case 0x08: + return CilType.I4; + case 0x09: + return CilType.U4; + case 0x0A: + return CilType.I8; + case 0x0B: + return CilType.U8; + case 0x0C: + return CilType.R4; + case 0x0D: + return CilType.R8; + case 0x0E: + return CilType.STRING; + case 0x0F: + return CilType.pointer(type()); + case 0x10: + return CilType.byRef(type()); + case 0x11: + return named(true); + case 0x12: + return named(false); + case 0x13: + return CilType.var(compressed()); + case 0x14: { + CilType elementType = type(); + int rank = compressed(); + int sizes = compressed(); + for (int i = 0; i < sizes; i++) { + compressed(); + } + int lowerBounds = compressed(); + for (int i = 0; i < lowerBounds; i++) { + compressed(); + } + return CilType.array(elementType, rank); + } + case 0x15: { + int definitionKind = md.u1(at++); + CilType definition = named(definitionKind == 0x11); + CilType[] args = new CilType[compressed()]; + for (int i = 0; i < args.length; i++) { + args[i] = type(); + } + return CilType.genericInst(definition, args); + } + case 0x16: + return CilType.TYPEDBYREF; + case 0x18: + return CilType.I; + case 0x19: + return CilType.U; + case 0x1B: + methodSig(); + return CilType.FNPTR; + case 0x1C: + return CilType.OBJECT; + case 0x1D: + return CilType.szArray(type()); + case 0x1E: + return CilType.mvar(compressed()); + case 0x1F: + case 0x20: + // A custom modifier (modreq/modopt) qualifies the type + // that follows it and does not change what that type is. + compressed(); + break; + case 0x41: + case 0x45: + // Sentinel and pinned: markers in front of a type. + break; + default: + throw bad("unknown element type 0x" + Integer.toHexString(element) + " in a signature"); + } + } + } + + CilType fieldSig() { + int lead = md.u1(at++); + if (lead != 0x06) { + throw bad("expected a field signature, found 0x" + Integer.toHexString(lead)); + } + return type(); + } + + CilAssembly.MethodSig methodSig() { + int lead = md.u1(at++); + boolean hasThis = (lead & 0x20) != 0; + int genericParams = (lead & 0x10) != 0 ? compressed() : 0; + CilType[] params = new CilType[compressed()]; + CilType returnType = type(); + for (int i = 0; i < params.length; i++) { + params[i] = type(); + } + return new CilAssembly.MethodSig(hasThis, genericParams, returnType, params); + } + + CilType[] locals() { + int lead = md.u1(at++); + if (lead != 0x07) { + throw bad("expected a local variable signature, found 0x" + Integer.toHexString(lead)); + } + CilType[] out = new CilType[compressed()]; + for (int i = 0; i < out.length; i++) { + out[i] = type(); + } + return out; + } + + CilType[] methodSpec() { + int lead = md.u1(at++); + if (lead != 0x0A) { + throw bad("expected a generic instantiation, found 0x" + Integer.toHexString(lead)); + } + CilType[] out = new CilType[compressed()]; + for (int i = 0; i < out.length; i++) { + out[i] = type(); + } + return out; + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/metadata/Universe.java b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/Universe.java new file mode 100644 index 00000000000..b752c0626e9 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/metadata/Universe.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.metadata; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/// Every assembly a translation can see: the ones being translated and the +/// reference assemblies they were compiled against. +/// +/// Types are looked up by full name alone. Which assembly a reference claims +/// the type lives in is ignored, because a compile-time reference names a +/// facade or a reference assembly, never the place the code ends up at run time. +public final class Universe { + private final List assemblies = new ArrayList(); + private final Map types = new HashMap(); + + /// The debug information of an assembly is the file of the same name + /// with `.pdb` where the assembly has `.dll` or `.exe`. + private static File pdbBeside(File assembly) { + String name = assembly.getName(); + int dot = name.lastIndexOf('.'); + return new File(assembly.getAbsoluteFile().getParentFile(), + (dot > 0 ? name.substring(0, dot) : name) + ".pdb"); + } + + /// Loads a set of assemblies together. The first to define a name wins, + /// so list the assemblies being translated before their references. + public List load(List files) throws IOException { + List loaded = new ArrayList(); + for (File file : files) { + CilAssembly assembly = new CilAssembly(Files.readAllBytes(file.toPath()), file.getName()); + File pdb = pdbBeside(file); + if (pdb.isFile()) { + assembly.debugInformation(PortablePdb.read(Files.readAllBytes(pdb.toPath()))); + } + assembly.load(this); + for (CilAssembly.TypeDef t : assembly.types()) { + if (!types.containsKey(t.fullName())) { + types.put(t.fullName(), t); + } + } + loaded.add(assembly); + } + for (CilAssembly assembly : loaded) { + assembly.linkBases(); + } + for (CilAssembly assembly : loaded) { + assembly.link(); + } + for (CilAssembly assembly : loaded) { + assembly.linkOverrides(); + } + assemblies.addAll(loaded); + return loaded; + } + + public List assemblies() { + return Collections.unmodifiableList(assemblies); + } + + public CilAssembly.TypeDef find(String fullName) { + return types.get(fullName); + } + + public CilAssembly.TypeDef require(String fullName, String neededBy) { + CilAssembly.TypeDef found = types.get(fullName); + if (found == null) { + throw new CilFormatException(neededBy + ": type " + fullName + + " is not defined by any loaded assembly (is a reference assembly missing?)"); + } + return found; + } + + /// The definition behind a type, or null for one that has none of its + /// own: an array, a type variable, a pointer. + public CilAssembly.TypeDef definitionOf(CilType type) { + String name = type.typeName(); + return name == null ? null : types.get(name); + } + + /// Finds the method a reference names, looking up the inheritance chain + /// when the named type only inherits it. Signatures are compared as + /// declared, type variables included, which is how a reference spells them. + public CilAssembly.MethodDef resolve(CilAssembly.MethodRef ref) { + if (ref.def != null) { + return ref.def; + } + CilAssembly.TypeDef type = definitionOf(ref.declaringType); + while (type != null) { + for (CilAssembly.MethodDef m : type.methods) { + if (m.name.equals(ref.name) && m.sig.sameShape(ref.sig)) { + return m; + } + } + type = type.baseType == null ? null : definitionOf(type.baseType); + } + return null; + } + + public CilAssembly.FieldDef resolve(CilAssembly.FieldRef ref) { + if (ref.def != null) { + return ref.def; + } + CilAssembly.TypeDef type = definitionOf(ref.declaringType); + while (type != null) { + CilAssembly.FieldDef f = type.field(ref.name); + if (f != null) { + return f; + } + type = type.baseType == null ? null : definitionOf(type.baseType); + } + return null; + } + + /// True when `type` is `ancestor` or derives from it, through base + /// classes or implemented interfaces. + public boolean derivesFrom(String type, String ancestor) { + if (type.equals(ancestor) || "System.Object".equals(ancestor)) { + return true; + } + CilAssembly.TypeDef def = types.get(type); + if (def == null) { + return false; + } + for (CilType i : def.interfaces) { + String name = i.typeName(); + if (name != null && derivesFrom(name, ancestor)) { + return true; + } + } + String base = def.baseType == null ? null : def.baseType.typeName(); + return base != null && derivesFrom(base, ancestor); + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/tools/MetadataDump.java b/maven/cil-translator/src/main/java/com/codename1/cil/tools/MetadataDump.java new file mode 100644 index 00000000000..2db148b5f14 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/tools/MetadataDump.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.tools; + +import com.codename1.cil.metadata.CilAssembly; +import com.codename1.cil.metadata.CilType; +import com.codename1.cil.metadata.MethodBody; +import com.codename1.cil.metadata.Universe; +import java.io.File; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +/// Prints what the metadata reader sees in an assembly, one line per type, +/// field and method. +/// +/// The format is shared with `scripts/unity-compat-samples/metadata-dump`, +/// which prints the same lines from .NET's own `System.Reflection.Metadata`. +/// Comparing the two outputs is how the reader is checked against an +/// independent implementation, so change both together. +public final class MetadataDump { + private MetadataDump() { + } + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println("usage: MetadataDump [reference assembly ...]"); + System.exit(2); + } + List files = new ArrayList(); + boolean il = false; + for (String arg : args) { + if ("--il".equals(arg)) { + il = true; + } else { + files.add(new File(arg)); + } + } + Universe universe = new Universe(); + CilAssembly assembly = universe.load(files).get(0); + // Flushed, not closed: closing it would close System.out. + PrintStream out = new PrintStream(System.out, false, "UTF-8"); // NOPMD CloseResource + if (il) { + dumpIl(assembly, out); + } else { + dump(assembly, out); + } + out.flush(); + } + + /// Lists every method body with its tokens resolved. A reading aid for + /// whoever works on the translator; nothing compares this output. + static void dumpIl(CilAssembly assembly, PrintStream out) { + for (CilAssembly.TypeDef t : assembly.types()) { + for (CilAssembly.MethodDef m : t.methods) { + out.println(m); + if (!m.hasBody()) { + continue; + } + MethodBody body = m.body(); + for (int i = 0; i < body.locals.length; i++) { + out.println(" local " + i + " : " + body.locals[i]); + } + for (MethodBody.Clause c : body.clauses) { + out.println(" clause flags=" + c.flags + " try=" + Integer.toHexString(c.tryStart) + ".." + + Integer.toHexString(c.tryEnd) + " handler=" + Integer.toHexString(c.handlerStart) + + ".." + Integer.toHexString(c.handlerEnd) + + (c.flags == MethodBody.Clause.CATCH ? " " + assembly.typeFromToken(c.classToken) : "")); + } + for (MethodBody.Instr in : body.instructions) { + out.println(" " + in + operand(assembly, in)); + } + } + } + out.flush(); + } + + private static String operand(CilAssembly assembly, MethodBody.Instr in) { + String op = in.op; + if ("call".equals(op) || "callvirt".equals(op) || "newobj".equals(op) || "ldftn".equals(op) + || "ldvirtftn".equals(op)) { + CilAssembly.MethodRef ref = assembly.methodRef(in.operand); + String inst = ""; + if (ref.methodArgs != null) { + inst = " <" + java.util.Arrays.toString(ref.methodArgs) + ">"; + } + return " " + ref + inst; + } + if ("ldfld".equals(op) || "ldflda".equals(op) || "stfld".equals(op) || "ldsfld".equals(op) + || "ldsflda".equals(op) || "stsfld".equals(op)) { + CilAssembly.FieldRef ref = assembly.fieldRef(in.operand); + return " " + ref + " : " + ref.type; + } + if ("ldstr".equals(op)) { + return " \"" + assembly.userString(in.operand) + "\""; + } + if ("ldtoken".equals(op)) { + return assembly.isFieldToken(in.operand) ? " field " + assembly.fieldRef(in.operand) : " token"; + } + if ("newarr".equals(op) || "box".equals(op) || "unbox".equals(op) || "unbox.any".equals(op) + || "castclass".equals(op) || "isinst".equals(op) || "initobj".equals(op) || "constrained.".equals(op) + || "ldobj".equals(op) || "stobj".equals(op) || "cpobj".equals(op) || "ldelem".equals(op) + || "stelem".equals(op) || "ldelema".equals(op) || "sizeof".equals(op)) { + return " " + assembly.typeFromToken(in.operand); + } + if ("ldc.i8".equals(op)) { + return " " + in.longOperand; + } + if ("ldc.r4".equals(op) || "ldc.r8".equals(op)) { + return " " + in.doubleOperand; + } + if ("switch".equals(op)) { + StringBuilder sb = new StringBuilder(" ["); + for (int target : in.targets) { + sb.append(" IL_").append(Integer.toHexString(target)); + } + return sb.append(" ]").toString(); + } + if (op.startsWith("b") && !"box".equals(op) && !"break".equals(op) || "leave".equals(op)) { + return " IL_" + Integer.toHexString(in.operand); + } + if (op.startsWith("ld") || op.startsWith("st")) { + if ("ldarg".equals(op) || "ldarga".equals(op) || "starg".equals(op) || "ldloc".equals(op) + || "ldloca".equals(op) || "stloc".equals(op) || "ldc.i4".equals(op)) { + return " " + in.operand; + } + } + return ""; + } + + static void dump(CilAssembly assembly, PrintStream out) { + for (CilAssembly.TypeDef t : assembly.types()) { + out.println("T " + t.fullName() + " flags=" + Integer.toHexString(t.flags) + " base=" + + (t.baseType == null ? "-" : plain(t.baseType)) + " generic=" + t.genericParamCount); + for (CilType i : t.interfaces) { + out.println(" I " + plain(i)); + } + for (CilAssembly.FieldDef f : t.fields) { + out.println(" F " + f.name + " flags=" + Integer.toHexString(f.flags) + " : " + f.type); + } + for (CilAssembly.MethodDef m : t.methods) { + String code = " code=-1 locals=0 clauses=0"; + if (m.hasBody()) { + MethodBody body = m.body(); + int size = 0; + if (!body.instructions.isEmpty()) { + size = body.instructions.get(body.instructions.size() - 1).next; + } + code = " code=" + size + " locals=" + body.locals.length + " clauses=" + body.clauses.size(); + } + out.println(" M " + m.name + " flags=" + Integer.toHexString(m.flags) + " " + m.sig + code); + } + } + out.flush(); + } + + // A base type or an implemented interface is named by a bare token, which + // carries no class-or-value-type marker; print the name alone so that the + // line does not depend on resolving it. + private static String plain(CilType type) { + String text = type.toString(); + return text.startsWith("valuetype ") ? text.substring("valuetype ".length()) : text; + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/translate/Jvm.java b/maven/cil-translator/src/main/java/com/codename1/cil/translate/Jvm.java new file mode 100644 index 00000000000..a2c179b520f --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/translate/Jvm.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.translate; + +import com.codename1.cil.metadata.CilType; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/// Opcode arithmetic: which JVM instruction handles a value of a given kind +/// or an array of a given element type. +final class Jvm implements Opcodes { + private Jvm() { + } + + static int loadOp(int kind) { + return kind == Val.REF ? ALOAD : ILOAD + kind; + } + + static int storeOp(int kind) { + return kind == Val.REF ? ASTORE : ISTORE + kind; + } + + static void pushInt(MethodVisitor mv, int value) { + if (value >= -1 && value <= 5) { + mv.visitInsn(ICONST_0 + value); + } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + mv.visitIntInsn(BIPUSH, value); + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + mv.visitIntInsn(SIPUSH, value); + } else { + mv.visitLdcInsn(Integer.valueOf(value)); + } + } + + static void pushDefault(MethodVisitor mv, int kind) { + switch (kind) { + case Val.I4: + mv.visitInsn(ICONST_0); + break; + case Val.I8: + mv.visitInsn(LCONST_0); + break; + case Val.R4: + mv.visitInsn(FCONST_0); + break; + case Val.R8: + mv.visitInsn(DCONST_0); + break; + default: + mv.visitInsn(ACONST_NULL); + break; + } + } + + /// Offset of an element type within the `xALOAD`/`xASTORE` families, + /// which are laid out int, long, float, double, reference, byte, char, + /// short. + private static int arrayOffset(CilType element) { + switch (element.kind) { + case BOOLEAN: + case I1: + case U1: + return 5; + case CHAR: + case U2: + return 6; + case I2: + return 7; + case I4: + case U4: + case I: + case U: + return 0; + case I8: + case U8: + return 1; + case R4: + return 2; + case R8: + return 3; + default: + return 4; + } + } + + static int arrayLoad(CilType element) { + return IALOAD + arrayOffset(element); + } + + static int arrayStore(CilType element) { + return IASTORE + arrayOffset(element); + } + + static int arrayTypeCode(CilType element) { + switch (element.kind) { + case BOOLEAN: + return T_BOOLEAN; + case CHAR: + case U2: + return T_CHAR; + case I1: + case U1: + return T_BYTE; + case I2: + return T_SHORT; + case I8: + case U8: + return T_LONG; + case R4: + return T_FLOAT; + case R8: + return T_DOUBLE; + default: + return T_INT; + } + } + + /// Boxes the primitive on top of the stack as its `java.lang` class. + static void box(MethodVisitor mv, Names names, CilType type) { + CilType p = names.norm(type); + if (p.kind == CilType.Kind.U1) { + // Byte.valueOf indexes a cache with its argument: 200 has to + // arrive as -56. + mv.visitInsn(I2B); + } + String c = Names.boxClass(p); + mv.visitMethodInsn(INVOKESTATIC, c, "valueOf", "(" + names.descriptor(p) + ")L" + c + ";", false); + } + + /// Unboxes a value known to be the right box: the result of a generic + /// member, not a cast the C# code asked for. + static void unbox(MethodVisitor mv, Names names, CilType type) { + CilType p = names.norm(type); + String c = Names.boxClass(p); + mv.visitTypeInsn(CHECKCAST, c); + mv.visitMethodInsn(INVOKEVIRTUAL, c, unboxMethod(p), "()" + names.descriptor(p), false); + if (p.kind == CilType.Kind.U1) { + pushInt(mv, 255); + mv.visitInsn(IAND); + } + } + + static String unboxMethod(CilType primitive) { + switch (primitive.kind) { + case BOOLEAN: + return "booleanValue"; + case CHAR: + case U2: + return "charValue"; + case I1: + case U1: + return "byteValue"; + case I2: + return "shortValue"; + case I8: + case U8: + return "longValue"; + case R4: + return "floatValue"; + case R8: + return "doubleValue"; + default: + return "intValue"; + } + } + + /// Suffix of the checked `Interop.unbox...` helper for a primitive. + static String unboxSuffix(CilType primitive) { + String m = unboxMethod(primitive); + String stem = m.substring(0, m.length() - "Value".length()); + return Character.toUpperCase(stem.charAt(0)) + stem.substring(1); + } + + /// The type an instruction suffix such as the `i4` of `ldelem.i4` names. + static CilType suffixType(String suffix) { + switch (suffix) { + case "i1": + return CilType.I1; + case "u1": + return CilType.U1; + case "i2": + return CilType.I2; + case "u2": + return CilType.U2; + case "i4": + return CilType.I4; + case "u4": + return CilType.U4; + case "i8": + return CilType.I8; + case "i": + return CilType.I; + case "r4": + return CilType.R4; + case "r8": + return CilType.R8; + case "ref": + return CilType.OBJECT; + default: + throw new TranslationException("unknown instruction suffix ." + suffix); + } + } + + /// Size of a primitive in an assembly's static data, or 0 when the type + /// is not one an array literal can hold. + static int byteSize(CilType primitive) { + switch (primitive.kind) { + case BOOLEAN: + case I1: + case U1: + return 1; + case CHAR: + case I2: + case U2: + return 2; + case I4: + case U4: + case R4: + return 4; + case I8: + case U8: + case R8: + return 8; + default: + return 0; + } + } +} diff --git a/maven/cil-translator/src/main/java/com/codename1/cil/translate/MethodTranslator.java b/maven/cil-translator/src/main/java/com/codename1/cil/translate/MethodTranslator.java new file mode 100644 index 00000000000..03af3f3b076 --- /dev/null +++ b/maven/cil-translator/src/main/java/com/codename1/cil/translate/MethodTranslator.java @@ -0,0 +1,2649 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.cil.translate; + +import com.codename1.cil.metadata.CilAssembly; +import com.codename1.cil.metadata.CilAssembly.FieldRef; +import com.codename1.cil.metadata.CilAssembly.MethodDef; +import com.codename1.cil.metadata.CilAssembly.MethodRef; +import com.codename1.cil.metadata.CilAssembly.MethodSig; +import com.codename1.cil.metadata.CilAssembly.TypeDef; +import com.codename1.cil.metadata.CilType; +import com.codename1.cil.metadata.MethodBody; +import com.codename1.cil.metadata.MethodBody.Clause; +import com.codename1.cil.metadata.MethodBody.Instr; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/// Lowers one CIL method body to JVM bytecode. +/// +/// The body is walked once, in IL order, with the type of every evaluation +/// stack entry tracked alongside. One pass is enough because ECMA-335 fixes +/// the stack at an instruction that is only reached by a backward branch to +/// be empty, so the state at every instruction is known by the time it is +/// reached: from the instruction before it, or from a forward branch already +/// seen. +/// +/// What the JVM has no word for is lowered as follows. +/// +/// - **Structs** are objects. A store copies into the object already there +/// (`$assign`) and a value that something else can still see is copied +/// before it is handed on; see [Val#own]. +/// - **Managed references** to a struct are the struct. To anything else they +/// are an array and an index, so a local whose address is taken lives in a +/// one-element array. +/// - **Exceptions**: all the catch clauses of one `try` share a JVM handler +/// that normalises the exception and tests each clause in turn. A `finally` +/// is a catch-all that records how it was entered and resumes from there. +/// - **Checks ParparVM does not make** -- a failed cast, an integer division +/// by zero -- are emitted explicitly. +final class MethodTranslator implements Opcodes { + private static final int PLAIN = 0; + private static final int PROMOTED = 1; + private static final int STRUCT = 2; + private static final int REF_STRUCT = 3; + private static final int REF_PAIR = 4; + + private static final class Var { + CilType type; + int slot; + int mode; + boolean addressTaken; + boolean stored; + } + + private static final class Group { + final List catches = new ArrayList(); + int tryStart; + int tryEnd; + int original; + int wrapped; + final Label dispatch = new Label(); + } + + private static final class Fin { + Clause clause; + int exception; + int resume; + final Label entry = new Label(); + final Label bodyLabel = new Label(); + final List