Skip to content

feat: let scenes offer marketplace items for credits - #9725

Open
juanmahidalgo wants to merge 10 commits into
devfrom
feat/open-item-purchase-from-scenes
Open

feat: let scenes offer marketplace items for credits#9725
juanmahidalgo wants to merge 10 commits into
devfrom
feat/open-item-purchase-from-scenes

Conversation

@juanmahidalgo

@juanmahidalgo juanmahidalgo commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Depends on the protocol contract in decentraland/protocol#462.

What this does

Adds the openItemPurchase restricted action, so an SDK7 scene can ask the client to sell a marketplace item for credits in-world — a vending machine, a shop counter, a quest reward — and learn the outcome so the world can react.

const { result } = await openItemPurchase({ urn: ITEM_URN })
if (result === OpenItemPurchaseResult.OIP_PURCHASED) dispense()

The scene supplies only the URN. The client resolves the price from the catalog, runs the same confirmation modal the passport already uses, signs and relays. Nothing about price, balance, credits or transactions crosses back — just a coarse verdict.

No new money code: the whole purchase pipeline is the existing one, reached from a second entry point.

Why it's shaped this way

  • URN only. A scene-supplied price would let it overcharge the player; a scene-supplied transaction could redirect the payment.
  • Coarse verdict. Every incomplete purchase is Failed, never a reason. Reporting "insufficient credits" separately would let scene code probe a wallet's balance by offering items at different prices, and the client already tells the player what happened.
  • Portable experiences are refused. A PX follows the player everywhere, so one able to raise a purchase confirmation in any parcel is a phishing surface. An offer belongs to the scene you are standing in. This is stricter than openExplorerUi, which has no equivalent check.
  • Gates mirror openExplorerUi: current scene + a user gesture within one tick. The gesture check was extracted into HasRecentUserGesture() and is now shared by both actions.
  • SceneItemPurchaseBridge is static for the same reason CreditsFeatureAccess is: the flow is one client-wide service whose owner initializes long after the scene runtime is built. It carries an interface (ISceneItemPurchaseFlow), so no assembly needs a new reference — and there are no asmdef changes in this PR.
  • Verdict comes from the modal's existing events, the ones analytics already consumes, rather than a new callback.

Notes for the reviewer

Five defects were found by running this against Amoy, not by reading it. Each has a comment at the site explaining the constraint, because none are locally obvious:

  1. The flow is driven from the scene runtime's thread, and everything it touches is main-thread only — the web request controller reads a PersistentSetting, LoadTextureAsync creates an entity in the global ECS world synchronously before its first await, and the modal is Unity UI. Hence the SwitchToMainThread before any of it. The passport reaches all of this from a UI callback, so it never had to switch.
  2. Releasing a thumbnail's Texture2DRef when its modal closed drove the texture's reference count negative. They are now kept keyed by url — which also bounds retention by distinct items rather than growing per offer.
  3. The purchase modal is a single instance shared with the passport, and MVCManager.ShowAsync returns silently when its controller is not hidden. Without the ViewHidden check, a busy modal was indistinguishable from the player dismissing the offer.
  4. For the same reason, an event may belong to somebody else's purchase, so each is matched against the listing this call opened. Otherwise a scene could observe Purchased from a passport purchase it never showed — and hand over an item nobody paid for.
  5. Closing the modal after a failed purchase also raises PurchaseCancelled (it is only suppressed on success), which overwrote Failed with Dismissed — a broken purchase reported back as if the player had declined. Terminal verdicts are now sticky.

Known cosmetic gap: the card's rarity frame and category icon stay unset, since those come from ScriptableObject mappings assigned per plugin in the Inspector. The modal skips them when null.

Follow-up

SceneItemPurchaseResult mirrors OpenItemPurchaseResult from the proto by hand, with the values in the same order. It is not generated yet because this repo pins a protocol build off experimental, while the proto change landed on main: regenerating from a main-based build here would revert the experimental-only changes to comms/rfc4, avatar_shape and light_source. Once a protocol bump carries the new type, swapping to it is a three-line change. That is what keeps this a draft.

Test plan

How to test:

  • Launch the client in .zone
  • Go to world spottygoat.dcl.eth or to parcel in 17,4
  • Check that the scene has a dispender in the center
  • Press on it
  • Verify it opens the buy modal
  • Buy the wearable
  • Verify the transaction completes succesfully

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below.

Name Link
Commit b02fe74
Logs https://github.com/decentraland/unity-explorer/actions/runs/31800182336
Download Windows https://github.com/decentraland/unity-explorer/suites/86270019699/artifacts/
Download Windows S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/feat/open-item-purchase-from-scenes/pr-25129-b02fe74/Decentraland_windows64.zip
Download Mac https://github.com/decentraland/unity-explorer/suites/86270019699/artifacts/9222000794
Download Mac S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/feat/open-item-purchase-from-scenes/pr-25129-b02fe74/Decentraland_macos.zip
Built on 2026-08-14T14:11:50Z

Lint

Warnings not reduced: 13156 => 13162 — remove at least 7 warnings to merge.

Warnings/errors in files changed by this PR (15)
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:57  CSharpWarnings::CS8618  Non-nullable fields 'rarityColorMappings', 'categoryIconsMapping', 'rarityBackgroundsMapping' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the fields as nullable.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:266  InconsistentNaming  Name 'CategoryIconsMapping' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'categoryIconsMapping'.
Assets/Protocol/DecentralandProtocol/ComponentID.gen.cs:4  InconsistentNaming  Name 'ComponentID' does not match rule 'members_should_be_pascal_case'. Suggested name is 'ComponentId'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:263  InconsistentNaming  Name 'CreditPurchasePopupPrefab' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'creditPurchasePopupPrefab'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:264  InconsistentNaming  Name 'CreditsTopUpPopupPrefab' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'creditsTopUpPopupPrefab'.
Assets/DCL/Infrastructure/SceneRuntime/Apis/Modules/RestrictedActionsApi/IRestrictedActionsAPI.cs:10  InconsistentNaming  Name 'IRestrictedActionsAPI' does not match rule 'interfaces_should_be_pascal_case_with_i_prefix'. Suggested name is 'IRestrictedActionsApi'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:267  InconsistentNaming  Name 'RarityBackgroundsMapping' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'rarityBackgroundsMapping'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:265  InconsistentNaming  Name 'RarityColorMappings' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'rarityColorMappings'.
Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/RestrictedActions/RestrictedActionsAPIImplementation.cs:29  InconsistentNaming  Name 'RestrictedActionsAPIImplementation' does not match rule 'members_should_be_pascal_case'. Suggested name is 'RestrictedActionsApiImplementation'.
Assets/DCL/Infrastructure/SceneRuntime/Apis/Modules/RestrictedActionsApi/RestrictedActionsAPIWrapper.cs:12  InconsistentNaming  Name 'RestrictedActionsAPIWrapper' does not match rule 'members_should_be_pascal_case'. Suggested name is 'RestrictedActionsApiWrapper'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:35  InconsistentNaming  Name 'marketplaceCreditsAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'marketplaceCreditsApiClient'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:61  InconsistentNaming  Name 'marketplaceCreditsAPIClient' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'marketplaceCreditsApiClient'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:39  InconsistentNaming  Name 'marketplaceShopAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'marketplaceShopApiClient'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:65  InconsistentNaming  Name 'marketplaceShopAPIClient' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'marketplaceShopApiClient'.
Assets/DCL/Infrastructure/SceneRuntime/Apis/Modules/RestrictedActionsApi/RestrictedActionsAPIWrapper.cs:87  RedundantNameQualifier  Qualifier is redundant

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 24986 0 13
PlayMode ✅ Passed 236 0 36

@davidejensen davidejensen self-assigned this Aug 14, 2026
@davidejensen
davidejensen marked this pull request as ready for review August 14, 2026 09:26
@davidejensen
davidejensen requested review from a team as code owners August 14, 2026 09:26
@decentraland-bot decentraland-bot added the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Aug 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Slack notification sent to #explorer-ext-contributions for external review.
To re-send, delete this comment and re-add the ext-contribution label.

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: feat: let scenes offer marketplace items for credits

STEP 2 — Root-cause check: PASS ✅

This is a new feature, not a bug fix. The diff adds a second entry point to the existing credit-purchase pipeline so SDK7 scenes can offer marketplace items in-world. The implementation reuses the existing modal, purchase service, and analytics events rather than duplicating any of them. No symptom-masking detected.

STEP 3 — Design & integration: PASS ✅

Owner search for SceneItemPurchaseBridge:

The lifecycle of the purchase flow is owned by CreditPurchasePlugin (created in InitializeAsync, torn down in Dispose()). The consumer is RestrictedActionsAPIImplementation, which lives in the DCL.CrdtEcsBridge assembly — constructed during scene runtime setup, before the plugin initializes, and in a different assembly. The static bridge pattern mirrors the existing CreditsFeatureAccess.Instance convention used for the same cross-assembly late-binding reason. Register/Unregister are correctly placed at the plugin's lifecycle boundaries.

Files searched: CreditPurchasePlugin.cs (owner), RestrictedActionsAPIImplementation.cs (consumer), CreditsFeatureAccess.cs (precedent), DynamicWorldContainer.cs (composition root).

CreditPurchasePlugin implementing ISceneItemPurchaseFlow directly is correct — the plugin already owns the modal controller, shop API client, image controller, and all required dependencies. Extracting a separate service would be a bridge-on-the-same-layer anti-pattern (CLAUDE.md §11).

Event subscription teardown trace:

  • PurchaseCompleted += OnCompletedPurchaseCompleted -= OnCompleted (in finally block, line 244) ✅
  • PurchaseFailed += OnFailedPurchaseFailed -= OnFailed (in finally block, line 245) ✅
  • PurchaseCancelled += OnCancelledPurchaseCancelled -= OnCancelled (in finally block, line 246) ✅

All subscriptions are properly cleaned up in a finally block — no leak.

SceneItemPurchaseBridge.Register / Unregister:

  • Register(this) at end of InitializeAsync (line 125) ✅
  • Unregister() at start of Dispose() (line 80) ✅

STEP 4 — Member audit

Member Consumers Verdict
SceneItemPurchaseBridge.IsAvailable 0 ❌ Dead code — see comment below
SceneItemPurchaseBridge.OpenAsync 1 (RestrictedActionsAPIImplementation) ✅ Justified — the bridge is the cross-assembly seam
SceneItemPurchaseBridge.Register 1 (CreditPurchasePlugin.InitializeAsync)
SceneItemPurchaseBridge.Unregister 1 (CreditPurchasePlugin.Dispose)
ISceneItemPurchaseFlow 1 impl (CreditPurchasePlugin) ✅ Justified for assembly decoupling (interface lives in CrdtEcsBridge, impl in PluginSystem)
HasRecentUserGesture() 2 (TryOpenExplorerUi, TryOpenItemPurchaseAsync) ✅ DRY extraction
CreditPurchaseModalControllerParams.SOURCE_SDK_SCENE 1 ✅ Named constant

STEP 5 — Line-level findings

See inline comments. All findings are P2.

Security review: PASS ✅

  • URN-only input: the scene supplies only a URN; price is resolved server-side from the catalog. No scene-supplied price or transaction data. ✅
  • Coarse verdict: a purchase that could not be completed is always OipFailed, never a reason. This prevents balance probing via differential pricing. ✅
  • Portable experience rejection: PX scenes are explicitly refused (sceneData.IsPortableExperience() check), preventing phishing from scenes that follow the player. Stricter than openExplorerUi. ✅
  • User gesture gate: requires a recent pointer gesture within USER_GESTURE_WINDOW_TICKS, preventing autonomous purchase prompts. ✅
  • Current-scene gate: sceneStateProvider.IsCurrent prevents background scenes from raising purchases. ✅
  • Event matching: ReferenceEquals(dto, listing) ensures each event belongs to the listing this call opened, preventing cross-purchase interference. ✅
  • No secrets or credentials in the diff.

STEP 6 — Complexity: COMPLEX

Touches plugin wiring, async/UniTask patterns, MVC modal lifecycle, cross-assembly concerns, JS module bridge, and protobuf protocol. 18 files changed, +1648 −105.

STEP 7 — QA: YES

Modifies runtime code: new user-facing purchase flow triggered from SDK scenes, shared modal with passport, thumbnail loading, and purchase verdict pipeline.

STEP 8 — Non-blocking warnings

None. Main.unity is not modified.


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches plugin wiring (CreditPurchasePlugin + DynamicWorldContainer), async/UniTask purchase flow, MVC modal lifecycle, cross-assembly static bridge, JS module system, and protobuf protocol generation.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by unknown via Slack

Comment thread Explorer/Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs Outdated
davidejensen and others added 3 commits August 14, 2026 12:23
Co-authored-by: Muna <44584806+decentraland-bot@users.noreply.github.com>
Signed-off-by: davidejensen <davidejensen@live.it>
…ementation/RestrictedActions/SceneItemPurchase.cs

Co-authored-by: Muna <44584806+decentraland-bot@users.noreply.github.com>
Signed-off-by: davidejensen <davidejensen@live.it>
@davidejensen davidejensen removed the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Aug 14, 2026

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat: let scenes offer marketplace items for credits

STEP 1 — Context & scope

Loaded CLAUDE.md, repo review instructions, and the relevant subsystem docs. The diff touches 18 files across 6 areas: restricted-actions API layer (CrdtEcsBridge), scene runtime wrapper, credit purchase plugin, protocol-generated protobuf code, the JS module bridge, and the protocol package dependency. The hand-authored C# changes span ~5 files; the rest are generated code and package manifests.

Surrounding context inspected: RestrictedActionsAPIWrapper.cs (full file — Dispose() pattern for CTS fields), RestrictedActionsAPIImplementation.cs (full file — gate-check patterns, HasRecentUserGesture), CreditPurchasePlugin.cs (full file from PR head — plugin lifecycle, OpenAsync, thumbnail cache, event wiring).

STEP 2 — Root-cause check

Feature PR — adds a new restricted action (openItemPurchase). Not a bug fix. PASS.

STEP 3 — Design & integration

Static bridge pattern (SceneItemPurchaseBridge):
The bridge follows the established CreditsFeatureAccess pattern for crossing the scene-runtime ↔ plugin assembly boundary. The scene runtime (RestrictedActionsAPIImplementation, CrdtEcsBridge assembly) is constructed per-scene before the global CreditPurchasePlugin (PluginSystem assembly) initializes. A static bridge is the canonical late-binding pattern for this construction order.

Lifecycle owners searched:

  • CreditPurchasePlugin.InitializeAsync → calls SceneItemPurchaseBridge.Register(this)found.
  • CreditPurchasePlugin.Dispose → calls SceneItemPurchaseBridge.Unregister()found.
  • The bridge itself is stateless (no persistent collections, no per-frame work). It does not reconcile, poll, or scan.

PASS — the bridge is not a new long-lived unit; it’s a service locator with proper registration/unregistration matching the plugin lifecycle.

Teardown / consumption trace:

Opener Mirror
SceneItemPurchaseBridge.Register(this) SceneItemPurchaseBridge.Unregister() in Dispose()
PurchaseCompleted += OnCompleted -= OnCompleted in finally
PurchaseFailed += OnFailed -= OnFailed in finally
PurchaseCancelled += OnCancelled -= OnCancelled in finally
thumbnailsByUrl (textures + sprites) Disposed and destroyed in Dispose()
openItemPurchaseCancellationToken (wrapper) NOT cancelled/disposed in Dispose() ⚠️ pre-existing pattern

Note on wrapper CTS lifecycle: openItemPurchaseCancellationToken follows the same pattern as movePlayerToCancellationToken and the other CTS fields in RestrictedActionsAPIWrapper — none are cancelled in Dispose(). The disposeCts from JsApiWrapper detaches the JS promise, but the underlying ct passed to TryOpenItemPurchaseAsync stays live. For the existing short-lived actions (move, emote) this is benign. For a purchase flow — which fetches a listing, loads a thumbnail, and shows a modal — the flow could continue after the scene unloads. Worth considering in a follow-up: linking the per-call CTS to disposeCts so the purchase flow cancels when the scene is torn down.

STEP 4 — Member audit

Member Consumers Verdict
HasRecentUserGesture() (private) 2 — TryOpenExplorerUi, TryOpenItemPurchaseAsync Good extraction. Shared logic, well-named.
ISceneItemPurchaseFlow.OpenAsync 1 impl (CreditPurchasePlugin), 1 consumer (bridge) Interface justified by assembly boundary.
SceneItemPurchaseBridge.Register 1 consumer (InitializeAsync) Registration endpoint.
SceneItemPurchaseBridge.Unregister 1 consumer (Dispose) Cleanup endpoint.
SceneItemPurchaseBridge.OpenAsync 1 consumer (TryOpenItemPurchaseAsync) Forwarding call.
SOURCE_SDK_SCENE 1 consumer (OpenAsync) Named constant for analytics source.

No single-use predicates re-deriving existing logic, no absent ≠ false/null issues.

STEP 5 — Line-level findings

See inline comments. Two P2 findings:

  1. Texture2DRef leak on cancellation between LoadTextureAsync and cache insertion
  2. Double blank line in SceneItemPurchase.cs

Security assessment

The security design is solid:

  • URN-only input prevents price manipulation — the client resolves the price from the catalog server-side.
  • Coarse verdict (Purchased/Dismissed/Failed) prevents balance probing — a scene cannot distinguish insufficient credits from other failures.
  • Portable experience rejection prevents phishing — a PX following the player everywhere cannot raise purchase confirmations in arbitrary parcels.
  • User gesture gate prevents programmatic spam — requires a pointer input within one tick.
  • ReferenceEquals(dto, listing) check prevents cross-purchase event contamination — a scene cannot observe a passport purchase it never initiated.
  • Sticky terminal verdicts prevent the PurchaseCancelled-after-PurchaseFailed bug from misreporting the outcome.

No secrets exposed, no injection risks (URN parsed by TryParseCollectionItem), no auth bypass paths.

STEP 6 — Complexity

COMPLEX — async flow across CrdtEcsBridge ↔ PluginSystem assemblies, modal event wiring with sticky verdicts, new protocol types, plugin lifecycle extension.

STEP 7 — QA assessment

YES — runtime code that triggers a user-facing purchase modal from scenes. Requires manual testing of the purchase flow, modal interaction, and edge cases (modal already visible, scene unload during flow).

STEP 8 — Non-blocking warnings

None. Main scene not modified.

STEP 9 — Verdict

Overall the PR is well-designed and carefully implemented. The security properties are strong, the async error handling is thorough, the sticky verdict logic correctly handles the PurchaseCancelled-after-PurchaseFailed edge case, and the HasRecentUserGesture extraction is a clean DRY refactor. The thumbnail cache with URL-keyed retention is a good solution to the ref-count issue.

Two P2 findings in the inline comments — both are low-impact improvements.

REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Async flow across CrdtEcsBridge ↔ PluginSystem assemblies, modal event wiring with sticky verdicts, new protocol types, plugin lifecycle extension.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by davidejensen via GitHub

Comment thread Explorer/Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs
@davidejensen davidejensen added the clean-build Used to trigger clean build on PR label Aug 14, 2026
@decentraland-bot

Copy link
Copy Markdown
Contributor

PR #9725, run #31808353580

Builds: Windows change, Windows baseline, macOS change, macOS baseline

How to read this table
  • Each build is measured 3 times. The values are the median, and (min–max) is the lowest and highest of those runs — a wide range means the metric is noisy and small differences are not trustworthy.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a real difference: larger than both 3% and the run-to-run range.
  • ⚪ within noise — the difference is smaller than how much the build varies between its own runs, so it cannot be told apart from random variation. Treat it as no change.
  • Exceptions per run — the average number of exceptions in a run's log; more than the baseline is flagged 🔴 even when frame times look fine. The Exception breakdown under each table groups them by the explorer's report category and exception type (as totals across the runs).
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.

Intel Core i5

Metric Baseline Change Δ Result
Samples 2331 (×3) 2423 (×3)
CPU average 38.3 ms (36.6–38.8) 37.0 ms (36.1–37.8) -1.3 ms ⚪ within noise
CPU 1% worst 380.0 ms (330.7–396.1) 287.8 ms (285.0–293.6) -92.1 ms 🟢 24% faster
CPU 0.1% worst 411.3 ms (351.0–424.5) 309.2 ms (302.7–317.5) -102.1 ms 🟢 25% faster
GPU average 8.0 ms (8.0–8.0) 9.6 ms (9.4–9.6) 1.6 ms 🔴 20% slower
GPU 1% worst 18.8 ms (18.6–19.3) 31.2 ms (29.4–32.1) 12.4 ms 🔴 66% slower
GPU 0.1% worst 19.2 ms (19.0–20.2) 38.7 ms (37.1–38.8) 19.6 ms 🔴 102% slower
Exceptions per run 66 66 0 ⚪ none new
Exception breakdown
Exception Baseline (3 runs) Change (3 runs)
[UI] DllNotFoundException 192 192
[ENGINE] NullReferenceException 3 3
[ENGINE] ObjectDisposedException 3 3

Apple M1

Metric Baseline Change Δ Result
Samples 4066 (×3) 4085 (×3)
CPU average 22.1 ms (21.9–22.1) 22.0 ms (21.3–22.2) -0.1 ms ⚪ within noise
CPU 1% worst 231.3 ms (196.1–233.3) 231.4 ms (155.3–231.6) 0.1 ms ⚪ within noise
CPU 0.1% worst 233.6 ms (231.9–238.3) 241.7 ms (236.7–244.3) 8.1 ms 🔴 3% slower
GPU average 2.5 ms (1.9–2.7) 2.9 ms (2.3–5.8) 0.3 ms ⚪ within noise
GPU 1% worst 34.6 ms (34.2–35.5) 34.3 ms (34.2–34.8) -0.3 ms ⚪ within noise
GPU 0.1% worst 35.7 ms (35.2–37.2) 35.8 ms (35.6–35.9) 0.0 ms ⚪ within noise
Exceptions per run 0 0 0 ⚪ none new

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clean-build Used to trigger clean build on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants