@uptimizr/unity 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +108 -0
- package/bridge/README.md +86 -0
- package/bridge/Uptimizr.jslib +95 -0
- package/bridge/UptimizrUnityBridge.cs +259 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/uptimizr-unity.global.js +58 -0
- package/dist/uptimizr-unity.js +58 -0
- package/package.json +64 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// UptimizrUnityBridge.cs — example engine-side bridge MonoBehaviour (copy-in asset).
|
|
2
|
+
//
|
|
3
|
+
// Part of the `@uptimizr/unity` connector (ADR 0045). This component is the **engine
|
|
4
|
+
// side** of the bridged capture tier: it samples Unity's own Camera pose, raycast
|
|
5
|
+
// picks, and frame timing each interval and pushes them across the WASM<->JS interop
|
|
6
|
+
// boundary (via the companion `Uptimizr.jslib` plugin) to the browser-side connector.
|
|
7
|
+
//
|
|
8
|
+
// It carries **no** analytics logic, IDs, or schema knowledge. Every world-space value
|
|
9
|
+
// is pushed in Unity's **native** frame (left-handed, y-up, meters); the connector —
|
|
10
|
+
// not this component — owns coordinate normalization and schema mapping (ADR 0018).
|
|
11
|
+
//
|
|
12
|
+
// Privacy (ADR 0003): only poses, FPS, and developer-named objects are sent. This
|
|
13
|
+
// component MUST NOT invent identifiers or forward raw input text. The pick channel
|
|
14
|
+
// sends only the hit GameObject's name (which you author) and the world hit point.
|
|
15
|
+
//
|
|
16
|
+
// Install:
|
|
17
|
+
// 1. Copy `Uptimizr.jslib` to `Assets/Plugins/WebGL/Uptimizr.jslib`.
|
|
18
|
+
// 2. Copy this file anywhere under `Assets/` and add the component to a GameObject.
|
|
19
|
+
// 3. On the host page, register the connector: `trackUnity({ ... })` (or
|
|
20
|
+
// `client.use(unityCollector())`) so `window.__uptimizr_unity__` exists.
|
|
21
|
+
//
|
|
22
|
+
// Uses the legacy `UnityEngine.Input` API for broad compatibility; adapt to the new
|
|
23
|
+
// Input System if your project has disabled the legacy one.
|
|
24
|
+
using System.Globalization;
|
|
25
|
+
using System.Runtime.InteropServices;
|
|
26
|
+
using System.Text;
|
|
27
|
+
using UnityEngine;
|
|
28
|
+
|
|
29
|
+
[DisallowMultipleComponent]
|
|
30
|
+
public class UptimizrUnityBridge : MonoBehaviour
|
|
31
|
+
{
|
|
32
|
+
// Must match BRIDGE_PROTOCOL_VERSION exported by `@uptimizr/web-export`.
|
|
33
|
+
const int ExpectedProtocolVersion = 1;
|
|
34
|
+
|
|
35
|
+
[Header("Camera pose")]
|
|
36
|
+
[Tooltip("Camera sampled for view-direction pose. Defaults to Camera.main.")]
|
|
37
|
+
public Camera targetCamera;
|
|
38
|
+
|
|
39
|
+
[Tooltip("Seconds between camera-pose samples.")]
|
|
40
|
+
public float poseSampleInterval = 0.25f;
|
|
41
|
+
|
|
42
|
+
[Header("Performance")]
|
|
43
|
+
[Tooltip("Seconds between performance (FPS / long-frame) reports.")]
|
|
44
|
+
public float perfReportInterval = 2f;
|
|
45
|
+
|
|
46
|
+
[Tooltip("Frame time (ms) above which a frame counts as a long / janky frame.")]
|
|
47
|
+
public float jankFrameMs = 50f;
|
|
48
|
+
|
|
49
|
+
[Header("Picks")]
|
|
50
|
+
[Tooltip("Raycast on primary pointer-down and push the named object that was hit.")]
|
|
51
|
+
public bool capturePicks = true;
|
|
52
|
+
|
|
53
|
+
[Tooltip("Layers eligible for pick raycasts.")]
|
|
54
|
+
public LayerMask pickMask = ~0;
|
|
55
|
+
|
|
56
|
+
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
57
|
+
[DllImport("__Internal")]
|
|
58
|
+
static extern int UptimizrUnityGetProtocolVersion();
|
|
59
|
+
|
|
60
|
+
[DllImport("__Internal")]
|
|
61
|
+
static extern void UptimizrUnityPushPose(
|
|
62
|
+
float px, float py, float pz,
|
|
63
|
+
float fx, float fy, float fz,
|
|
64
|
+
float ux, float uy, float uz,
|
|
65
|
+
float fov);
|
|
66
|
+
|
|
67
|
+
[DllImport("__Internal")]
|
|
68
|
+
static extern void UptimizrUnityPushPick(string objectName, float hx, float hy, float hz);
|
|
69
|
+
|
|
70
|
+
[DllImport("__Internal")]
|
|
71
|
+
static extern void UptimizrUnityPushPerf(float fps, int longFrames);
|
|
72
|
+
|
|
73
|
+
[DllImport("__Internal")]
|
|
74
|
+
static extern void UptimizrUnitySetSceneProxy(string json);
|
|
75
|
+
#else
|
|
76
|
+
// Editor / non-WebGL stubs so the component compiles and runs in the Editor and in
|
|
77
|
+
// native builds (where there is no JS bridge to push to).
|
|
78
|
+
static int UptimizrUnityGetProtocolVersion() => ExpectedProtocolVersion;
|
|
79
|
+
|
|
80
|
+
static void UptimizrUnityPushPose(
|
|
81
|
+
float px, float py, float pz,
|
|
82
|
+
float fx, float fy, float fz,
|
|
83
|
+
float ux, float uy, float uz,
|
|
84
|
+
float fov) { }
|
|
85
|
+
|
|
86
|
+
static void UptimizrUnityPushPick(string objectName, float hx, float hy, float hz) { }
|
|
87
|
+
|
|
88
|
+
static void UptimizrUnityPushPerf(float fps, int longFrames) { }
|
|
89
|
+
|
|
90
|
+
static void UptimizrUnitySetSceneProxy(string json) { }
|
|
91
|
+
#endif
|
|
92
|
+
|
|
93
|
+
float poseTimer;
|
|
94
|
+
float perfTimer;
|
|
95
|
+
int frameCount;
|
|
96
|
+
int longFrameCount;
|
|
97
|
+
|
|
98
|
+
void Start()
|
|
99
|
+
{
|
|
100
|
+
int version = UptimizrUnityGetProtocolVersion();
|
|
101
|
+
if (version != ExpectedProtocolVersion)
|
|
102
|
+
{
|
|
103
|
+
Debug.LogWarning(
|
|
104
|
+
"[Uptimizr] bridge protocol mismatch: this shim expects v" +
|
|
105
|
+
ExpectedProtocolVersion + " but the connector reports v" + version +
|
|
106
|
+
". Make sure trackUnity()/unityCollector() is registered on the host " +
|
|
107
|
+
"page before the export loads. Disabling the bridge.");
|
|
108
|
+
enabled = false;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (targetCamera == null)
|
|
113
|
+
{
|
|
114
|
+
targetCamera = Camera.main;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
void Update()
|
|
119
|
+
{
|
|
120
|
+
float dt = Time.unscaledDeltaTime;
|
|
121
|
+
|
|
122
|
+
// Performance: accumulate over a window, then report measured FPS + long frames.
|
|
123
|
+
frameCount++;
|
|
124
|
+
if (dt * 1000f > jankFrameMs)
|
|
125
|
+
{
|
|
126
|
+
longFrameCount++;
|
|
127
|
+
}
|
|
128
|
+
perfTimer += dt;
|
|
129
|
+
if (perfTimer >= perfReportInterval && perfTimer > 0f)
|
|
130
|
+
{
|
|
131
|
+
float fps = frameCount / perfTimer;
|
|
132
|
+
UptimizrUnityPushPerf(fps, longFrameCount);
|
|
133
|
+
perfTimer = 0f;
|
|
134
|
+
frameCount = 0;
|
|
135
|
+
longFrameCount = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Camera cam = targetCamera != null ? targetCamera : Camera.main;
|
|
139
|
+
if (cam == null)
|
|
140
|
+
{
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Camera pose: position / forward / up in Unity's native frame; FOV in radians.
|
|
145
|
+
poseTimer += dt;
|
|
146
|
+
if (poseTimer >= poseSampleInterval)
|
|
147
|
+
{
|
|
148
|
+
poseTimer = 0f;
|
|
149
|
+
Transform t = cam.transform;
|
|
150
|
+
Vector3 p = t.position;
|
|
151
|
+
Vector3 f = t.forward;
|
|
152
|
+
Vector3 u = t.up;
|
|
153
|
+
float fovRad = cam.fieldOfView * Mathf.Deg2Rad;
|
|
154
|
+
UptimizrUnityPushPose(p.x, p.y, p.z, f.x, f.y, f.z, u.x, u.y, u.z, fovRad);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Picks: on primary pointer-down, raycast and push the named object + hit point.
|
|
158
|
+
if (capturePicks && Input.GetMouseButtonDown(0))
|
|
159
|
+
{
|
|
160
|
+
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
|
|
161
|
+
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, pickMask))
|
|
162
|
+
{
|
|
163
|
+
Vector3 hp = hit.point;
|
|
164
|
+
UptimizrUnityPushPick(hit.collider.gameObject.name, hp.x, hp.y, hp.z);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// <summary>
|
|
170
|
+
/// Optional one-shot: push the world-space AABBs of the given renderers as the
|
|
171
|
+
/// scene proxy (for spatial context + replay). Call once after your scene is
|
|
172
|
+
/// loaded. Object names come from your GameObjects — keep them non-PII (ADR 0003).
|
|
173
|
+
/// </summary>
|
|
174
|
+
public void PushSceneProxy(Renderer[] renderers)
|
|
175
|
+
{
|
|
176
|
+
if (renderers == null)
|
|
177
|
+
{
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
var sb = new StringBuilder();
|
|
182
|
+
sb.Append('[');
|
|
183
|
+
bool first = true;
|
|
184
|
+
foreach (Renderer r in renderers)
|
|
185
|
+
{
|
|
186
|
+
if (r == null)
|
|
187
|
+
{
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
Bounds b = r.bounds;
|
|
191
|
+
Vector3 min = b.min;
|
|
192
|
+
Vector3 max = b.max;
|
|
193
|
+
if (!first)
|
|
194
|
+
{
|
|
195
|
+
sb.Append(',');
|
|
196
|
+
}
|
|
197
|
+
first = false;
|
|
198
|
+
sb.Append("{\"name\":");
|
|
199
|
+
AppendJsonString(sb, r.gameObject.name);
|
|
200
|
+
sb.Append(",\"aabb\":[");
|
|
201
|
+
AppendFloat(sb, min.x);
|
|
202
|
+
sb.Append(',');
|
|
203
|
+
AppendFloat(sb, min.y);
|
|
204
|
+
sb.Append(',');
|
|
205
|
+
AppendFloat(sb, min.z);
|
|
206
|
+
sb.Append(',');
|
|
207
|
+
AppendFloat(sb, max.x);
|
|
208
|
+
sb.Append(',');
|
|
209
|
+
AppendFloat(sb, max.y);
|
|
210
|
+
sb.Append(',');
|
|
211
|
+
AppendFloat(sb, max.z);
|
|
212
|
+
sb.Append("]}");
|
|
213
|
+
}
|
|
214
|
+
sb.Append(']');
|
|
215
|
+
UptimizrUnitySetSceneProxy(sb.ToString());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static void AppendFloat(StringBuilder sb, float v)
|
|
219
|
+
{
|
|
220
|
+
sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
static void AppendJsonString(StringBuilder sb, string s)
|
|
224
|
+
{
|
|
225
|
+
sb.Append('"');
|
|
226
|
+
foreach (char c in s)
|
|
227
|
+
{
|
|
228
|
+
switch (c)
|
|
229
|
+
{
|
|
230
|
+
case '"':
|
|
231
|
+
sb.Append("\\\"");
|
|
232
|
+
break;
|
|
233
|
+
case '\\':
|
|
234
|
+
sb.Append("\\\\");
|
|
235
|
+
break;
|
|
236
|
+
case '\n':
|
|
237
|
+
sb.Append("\\n");
|
|
238
|
+
break;
|
|
239
|
+
case '\r':
|
|
240
|
+
sb.Append("\\r");
|
|
241
|
+
break;
|
|
242
|
+
case '\t':
|
|
243
|
+
sb.Append("\\t");
|
|
244
|
+
break;
|
|
245
|
+
default:
|
|
246
|
+
if (c < ' ')
|
|
247
|
+
{
|
|
248
|
+
sb.Append("\\u").Append(((int)c).ToString("x4"));
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
{
|
|
252
|
+
sb.Append(c);
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
sb.Append('"');
|
|
258
|
+
}
|
|
259
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EngineBridge, NativeFrame, TrackWebExportOptions, WebExportCollectorOptions, WebExportSession } from "@uptimizr/web-export";
|
|
2
|
+
import type { Collector } from "@uptimizr/sdk-core";
|
|
3
|
+
/** Unity's native world coordinate frame: left-handed, y-up, meters (canonical). */
|
|
4
|
+
export declare const UNITY_FRAME: NativeFrame;
|
|
5
|
+
/** The engine id used for connector provenance and the collector name. */
|
|
6
|
+
export declare const UNITY_CONNECTOR_NAME = "unity";
|
|
7
|
+
export type UnityCollectorOptions = Omit<WebExportCollectorOptions, "name" | "frame">;
|
|
8
|
+
export type TrackUnityOptions = Omit<TrackWebExportOptions, "name" | "frame">;
|
|
9
|
+
/**
|
|
10
|
+
* The Unity collector — register it with an sdk-core client via `client.use(...)`.
|
|
11
|
+
* Wires the JS-only tier and exposes the engine bridge (default global
|
|
12
|
+
* `window.__uptimizr_unity__`) for the engine-side shim.
|
|
13
|
+
*/
|
|
14
|
+
export declare function unityCollector(options?: UnityCollectorOptions): Collector;
|
|
15
|
+
/**
|
|
16
|
+
* One-call Unity integration: create a client, register {@link unityCollector}, and
|
|
17
|
+
* start the session with Unity's connector provenance (ADR 0018). Returns the client
|
|
18
|
+
* and the {@link EngineBridge} the Unity shim pushes through.
|
|
19
|
+
*/
|
|
20
|
+
export declare function trackUnity(options: TrackUnityOptions): WebExportSession;
|
|
21
|
+
export type { EngineBridge };
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,yBAAyB,EACzB,gBAAgB,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,oFAAoF;AACpF,eAAO,MAAM,WAAW,EAAE,WAA+D,CAAC;AAE1F,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,yBAAyB,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AACtF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AAE9E;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,SAAS,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAEvE;AAED,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@uptimizr/unity` — the Unity (WebGL export) connector for Uptimizr (ADR 0045).
|
|
3
|
+
*
|
|
4
|
+
* Unity compiles to WebAssembly and renders into a `<canvas>`, so there is no live
|
|
5
|
+
* JS scene to read. This connector is **two-part**:
|
|
6
|
+
*
|
|
7
|
+
* - a **JS-only tier** (this package, no engine code) — pointer heatmaps, rAF FPS,
|
|
8
|
+
* and error capture straight from the canvas DOM; and
|
|
9
|
+
* - a **bridged tier** — a thin engine-side shim (a `.jslib` plugin + a small
|
|
10
|
+
* `MonoBehaviour`, see `bridge/`) pushes camera pose / picks / perf over the
|
|
11
|
+
* versioned {@link EngineBridge} for view-direction heatmaps, world-space gaze,
|
|
12
|
+
* and replay.
|
|
13
|
+
*
|
|
14
|
+
* Unity's native world frame is **left-handed, y-up, meters** — already Uptimizr's
|
|
15
|
+
* canonical wire frame, so world-space payloads need no axis conversion (the
|
|
16
|
+
* normalization is the identity for Unity).
|
|
17
|
+
*/
|
|
18
|
+
import { trackWebExport, webExportCollector } from "@uptimizr/web-export";
|
|
19
|
+
/** Unity's native world coordinate frame: left-handed, y-up, meters (canonical). */
|
|
20
|
+
export const UNITY_FRAME = { handedness: "left", upAxis: "y", unitScale: 1 };
|
|
21
|
+
/** The engine id used for connector provenance and the collector name. */
|
|
22
|
+
export const UNITY_CONNECTOR_NAME = "unity";
|
|
23
|
+
/**
|
|
24
|
+
* The Unity collector — register it with an sdk-core client via `client.use(...)`.
|
|
25
|
+
* Wires the JS-only tier and exposes the engine bridge (default global
|
|
26
|
+
* `window.__uptimizr_unity__`) for the engine-side shim.
|
|
27
|
+
*/
|
|
28
|
+
export function unityCollector(options = {}) {
|
|
29
|
+
return webExportCollector({ ...options, name: UNITY_CONNECTOR_NAME, frame: UNITY_FRAME });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One-call Unity integration: create a client, register {@link unityCollector}, and
|
|
33
|
+
* start the session with Unity's connector provenance (ADR 0018). Returns the client
|
|
34
|
+
* and the {@link EngineBridge} the Unity shim pushes through.
|
|
35
|
+
*/
|
|
36
|
+
export function trackUnity(options) {
|
|
37
|
+
return trackWebExport({ ...options, name: UNITY_CONNECTOR_NAME, frame: UNITY_FRAME });
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAU1E,oFAAoF;AACpF,MAAM,CAAC,MAAM,WAAW,GAAgB,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AAE1F,0EAA0E;AAC1E,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAK5C;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,UAAiC,EAAE;IAChE,OAAO,kBAAkB,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;AAC5F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,OAAO,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;AACxF,CAAC"}
|