Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ namespace SceneRuntime.Apis.Modules.EngineApi
public class EngineApiWrapper : JsApiWrapper<IEngineApi>
{
private readonly IInstancePoolsProvider instancePoolsProvider;
private readonly IJsOperations jsOperations;
protected readonly ISceneExceptionsHandler exceptionsHandler;
private readonly SceneRuntimeMetrics metrics;
private readonly string threadName;
private PoolableByteArray lastInput = PoolableByteArray.EMPTY;

public EngineApiWrapper(IEngineApi api, ISceneData sceneData, IInstancePoolsProvider instancePoolsProvider, ISceneExceptionsHandler exceptionsHandler, SceneRuntimeMetrics metrics, CancellationTokenSource disposeCts)
public EngineApiWrapper(IEngineApi api, ISceneData sceneData, IInstancePoolsProvider instancePoolsProvider, IJsOperations jsOperations, ISceneExceptionsHandler exceptionsHandler, SceneRuntimeMetrics metrics, CancellationTokenSource disposeCts)
: base(api, disposeCts)
{
this.instancePoolsProvider = instancePoolsProvider;
this.jsOperations = jsOperations;
this.exceptionsHandler = exceptionsHandler;
this.metrics = metrics;
threadName = $"CrdtSendToRenderer({sceneData.SceneShortInfo})";
Expand All @@ -34,10 +36,10 @@ protected override void DisposeInternal()
}

[UsedImplicitly]
public PoolableByteArray CrdtSendToRenderer(ITypedArray<byte> data)
public ITypedArray<byte>? CrdtSendToRenderer(ITypedArray<byte> data)
{
if (disposeCts.IsCancellationRequested)
return PoolableByteArray.EMPTY;
return null;

try
{
Expand All @@ -53,7 +55,7 @@ public PoolableByteArray CrdtSendToRenderer(ITypedArray<byte> data)

Profiler.EndThreadProfiling();

return result.IsEmpty ? PoolableByteArray.EMPTY : result;
return ToScriptArray(ref result);
}
catch (Exception e)
{
Expand All @@ -62,30 +64,50 @@ public PoolableByteArray CrdtSendToRenderer(ITypedArray<byte> data)
// Report an uncategorized MANAGED exception (don't propagate it further)
exceptionsHandler.OnEngineException(e);

return PoolableByteArray.EMPTY;
return null;
}
}

[UsedImplicitly]
public PoolableByteArray CrdtGetState()
public ITypedArray<byte>? CrdtGetState()
{
if (disposeCts.IsCancellationRequested)
return PoolableByteArray.EMPTY;
return null;

try
{
PoolableByteArray result = api.CrdtGetState();
metrics.BytesToScene.Add(result.Length);
return result.IsEmpty ? PoolableByteArray.EMPTY : result;
return ToScriptArray(ref result);
}
catch (Exception e)
{
// Report an uncategorized MANAGED exception (don't propagate it further)
exceptionsHandler.OnEngineException(e);
return PoolableByteArray.EMPTY;
return null;
}
}

/// <summary>
/// Copies the payload into a script-owned Uint8Array in a single bulk write and returns the pooled
/// array deterministically. Previously the <see cref="PoolableByteArray" /> itself was returned to JS:
/// it was consumed through the per-byte fast-proxy enumerator (one host transition per byte in
/// <c>new Uint8Array(...)</c>) and never disposed, so the shared bytes pool never got its arrays back.
/// </summary>
private ITypedArray<byte>? ToScriptArray(ref PoolableByteArray result)
{
if (result.IsEmpty)
{
result.Dispose();
return null;
}

ITypedArray<byte> scriptArray = jsOperations.NewUint8Array(result.Length);
scriptArray.WriteBytes(result.Array, 0ul, (ulong)result.Length, 0ul);
result.Dispose();
return scriptArray;
}

[UsedImplicitly]
public virtual PoolableSDKObservableEventArray? SendBatch() => null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ public SDKObservableEventsEngineApiWrapper(ISDKObservableEventsEngineApi api,
ISceneData sceneData,
ISDKMessageBusCommsControllerAPI commsApi,
IInstancePoolsProvider instancePoolsProvider,
IJsOperations jsOperations,
ISceneExceptionsHandler exceptionsHandler,
SceneRuntimeMetrics metrics,
CancellationTokenSource disposeCts)
: base(api, sceneData, instancePoolsProvider, exceptionsHandler, metrics, disposeCts)
: base(api, sceneData, instancePoolsProvider, jsOperations, exceptionsHandler, metrics, disposeCts)
{
engineApi = api;
this.commsApi = commsApi;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ public interface ISceneRuntime : IDisposable
void RegisterEngineAPIWrapper(EngineApiWrapper newWrapper);

V8RuntimeHeapInfo RuntimeHeapInfo { get; }

/// <summary>
/// Script-bound array operations of the underlying engine,
/// required by the API wrappers that marshal binary data back to the scene
/// </summary>
IJsOperations JsOperations { get; }
}

public static class SceneRuntimeExtensions
Expand Down Expand Up @@ -148,14 +154,14 @@ SceneRuntimeMetrics runtimeMetrics

internal static void RegisterEngineAPI(this ISceneRuntime sceneRuntime, ISceneData sceneData, IEngineApi engineApi, IInstancePoolsProvider instancePoolsProvider, ISceneExceptionsHandler sceneExceptionsHandler, SceneRuntimeMetrics runtimeMetrics)
{
var newWrapper = new EngineApiWrapper(engineApi, sceneData, instancePoolsProvider, sceneExceptionsHandler, runtimeMetrics, sceneRuntime.isDisposingTokenSource);
var newWrapper = new EngineApiWrapper(engineApi, sceneData, instancePoolsProvider, sceneRuntime.JsOperations, sceneExceptionsHandler, runtimeMetrics, sceneRuntime.isDisposingTokenSource);
sceneRuntime.Register("UnityEngineApi", newWrapper);
sceneRuntime.RegisterEngineAPIWrapper(newWrapper);
}

internal static void RegisterEngineAPI(this ISceneRuntime sceneRuntime, ISceneData sceneData, ISDKObservableEventsEngineApi engineApi, ISDKMessageBusCommsControllerAPI commsApiImplementation, IInstancePoolsProvider instancePoolsProvider, ISceneExceptionsHandler sceneExceptionsHandler, SceneRuntimeMetrics runtimeMetrics)
{
var newWrapper = new SDKObservableEventsEngineApiWrapper(engineApi, sceneData, commsApiImplementation, instancePoolsProvider, sceneExceptionsHandler, runtimeMetrics, sceneRuntime.isDisposingTokenSource);
var newWrapper = new SDKObservableEventsEngineApiWrapper(engineApi, sceneData, commsApiImplementation, instancePoolsProvider, sceneRuntime.JsOperations, sceneExceptionsHandler, runtimeMetrics, sceneRuntime.isDisposingTokenSource);
sceneRuntime.Register("UnityEngineApi", newWrapper);
sceneRuntime.RegisterEngineAPIWrapper(newWrapper);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public sealed class SceneRuntimeImpl : ISceneRuntime, IJsOperations

public V8RuntimeHeapInfo RuntimeHeapInfo { get; private set; }

public IJsOperations JsOperations => this;

CancellationTokenSource ISceneRuntime.isDisposingTokenSource => isDisposingTokenSource;

public SceneRuntimeImpl(
Expand Down
10 changes: 6 additions & 4 deletions Explorer/Assets/StreamingAssets/Js/Modules/EngineApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// https://github.com/decentraland/protocol/blob/main/proto/decentraland/kernel/apis/engine_api.proto

module.exports.crdtSendToRenderer = async function(messages) {
const data = new Uint8Array(UnityEngineApi.CrdtSendToRenderer(messages.data))
// The returned value is already a Uint8Array created and bulk-filled on the C# side:
// wrapping it in `new Uint8Array(...)` would copy it byte by byte through the interop boundary
const data = UnityEngineApi.CrdtSendToRenderer(messages.data)
return {
data: [data]
data: data ? [data] : []
};
}

Expand All @@ -22,9 +24,9 @@ module.exports.sendBatch = async function() {
}

module.exports.crdtGetState = async function() {
const data = new Uint8Array(UnityEngineApi.CrdtGetState())
const data = UnityEngineApi.CrdtGetState()
return {
data: [data],
data: data ? [data] : [],
hasEntities: true //TODO replace with actual value
};
}
Expand Down
Loading