@Singtaa: Please consider leaving a review on the Asset Store if you enjoy using OneJS. It really helps us a lot! And, I promise it'll take less time than debugging your last script.😊 Oh also, OneJS V2 is now out of preview!
VERSION
Doc Menu

Rainbow Bars

Friendly reminder: premature optimization is the root of all evil.

This tutorial shows you how to optimize performance and eliminate GC allocation during interop in OneJS by using PuerTS's Static Wrappers and BlittableCopy.

Let's start with rendering some bars every frame using the Vector API:

TSX
import { Color, Vector2 } from "UnityEngine"
import { MeshGenerationContext } from "UnityEngine/UIElements"

var div = document.createElement("div")
div.style.width = "100%"
div.style.height = "100%"

const NUM_BARS = 500
const BAR_LINE_WIDTH = 3
div.ve.generateVisualContent = (ctx: MeshGenerationContext) => {
    const { width, height } = ctx.visualElement.contentRect;
    const barWidth = width / NUM_BARS;
    const halfBarWidth = barWidth / 2;
    const painter = ctx.painter2D;

    painter.lineWidth = BAR_LINE_WIDTH;
    for (let i = 0; i < NUM_BARS; i++) {
        const x = barWidth * i + halfBarWidth;
        painter.strokeColor = Color.HSVToRGB(i / NUM_BARS, 1, 1);
        painter.BeginPath();
        painter.MoveTo(new Vector2(x, height));
        painter.LineTo(new Vector2(x, height * Math.random()));
        painter.Stroke();
    }
}

document.body.appendChild(div)
setInterval(() => { div.ve.MarkDirtyRepaint() })

Depending on your computer specs, you may notice a lag spike. If not, you can increase the NUM_BARS count. From the Profiler, you'll see the spike is due to garbage generation inside of GenerateVisualContent.

This happens because, by default, the JS-to-C# calls will use reflection. PuerTS provides a way to generate wrappers for these calls so that JS can call them directly.

  • Enable Allow 'unsafe' Code in Player Settings (This is needed for the BlittableCopy memory optimization)
  • Create a Config class like the following anywhere in your project (note the use of the various Attributes). We'll include all the C# classes and structs that our JS code referenced, as well as some common Unity structs like Vector3 and Quaternion.
CS
using System;
using System.Collections.Generic;
using Puerts;
using UnityEngine.UIElements;

namespace MyGame {
    [Configure]
    public class MyCustomPuertsCfg {
        [CodeOutputDirectory]
        static string OutputDir => UnityEngine.Application.dataPath + "/_gen/";
        
        // Declare here the C# types that your JS code may reference. 
        // Generally, you want to focus on types on hot paths.
        [Binding]
        static IEnumerable<Type> Bindings {
            get {
                return new List<Type>() {
                    typeof(UnityEngine.Rect),
                    typeof(UnityEngine.Color),
                    typeof(UnityEngine.Color32),
                    typeof(UnityEngine.Vector2),
                    typeof(UnityEngine.Vector3),
                    typeof(UnityEngine.Quaternion),
                    typeof(VisualElement),
                    typeof(MeshGenerationContext),
                    typeof(Painter2D),
                    typeof(OneJS.Dom.Document),
                    typeof(OneJS.Dom.Dom),
                    typeof(OneJS.Dom.DomStyle),
                };
            }
        }

        // Declare here any struct you'll be using during interop. PuerTS will 
        // optimize the memory usage, eliminating GC in most cases.
        [BlittableCopy]
        static IEnumerable<Type> Blittables {
            get {
                return new List<Type>() {
                    typeof(UnityEngine.Rect),
                    typeof(UnityEngine.Color),
                    typeof(UnityEngine.Color32),
                    typeof(UnityEngine.Vector2),
                    typeof(UnityEngine.Vector3),
                    typeof(UnityEngine.Quaternion),
                };
            }
        }
    }
}
  • Use the Menu Item Tools > OneJS > Generate StaticWrappers

Now if you run the Profiler again (preferably targeting the standalone player), you'll see major performance improvements and zero GC.Alloc.

As you can see, PuerTS makes optimization fairly easy to do. And for the most part, you don't need to worry about generating StaticWrappers during development. Usually, you can just default to using Reflection and only generate StaticWrappers before a production build.