docs
Loading...
Searching...
No Matches
HolderTraits.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
6#include <Ultralight/RefPtr.h>
7
8#include <memory>
9#include <type_traits>
10
11namespace ultralight {
12
13///
14/// Whether a holder shares ownership of a bound-class instance or owns it alone.
15///
16enum class HolderKind {
17 ///
18 /// Copies share ownership (eg, RefPtr), or the instance is never destroyed (js::Eternal).
19 ///
21
22 ///
23 /// One holder owns the instance and ownership moves (eg, std::unique_ptr). Returning one to
24 /// JavaScript leaves it empty, and it can't be a bound parameter.
25 ///
27};
28
29/// \cond INTERNAL
30namespace detail {
31
32// The library's built-in holders. HolderTraits inherits this tier, so a user specialization of
33// HolderTraits always takes priority, and a release can add built-ins here without colliding
34// with specializations users already wrote.
35template <typename H, typename = void>
36struct BuiltinHolderTraits {};
37
38template <typename T>
39struct BuiltinHolderTraits<RefPtr<T>> {
40 using element_type = T;
41 static constexpr HolderKind kind = HolderKind::Shared;
42 static T* Get(const RefPtr<T>& holder) { return holder.get(); }
43};
44
45template <typename T>
46struct BuiltinHolderTraits<std::shared_ptr<T>> {
47 using element_type = T;
48 static constexpr HolderKind kind = HolderKind::Shared;
49 static T* Get(const std::shared_ptr<T>& holder) { return holder.get(); }
50};
51
52template <typename T, typename D>
53struct BuiltinHolderTraits<std::unique_ptr<T, D>> {
54 using element_type = T;
55 static constexpr HolderKind kind = HolderKind::Exclusive;
56 static T* Get(const std::unique_ptr<T, D>& holder) { return holder.get(); }
57};
58
59template <typename T>
60struct BuiltinHolderTraits<WeakPtr<T>> {
61 using element_type = T;
62 static RefPtr<T> Lock(const WeakPtr<T>& holder) { return holder.Lock(); }
63};
64
65template <typename T>
66struct BuiltinHolderTraits<std::weak_ptr<T>> {
67 using element_type = T;
68 static std::shared_ptr<T> Lock(const std::weak_ptr<T>& holder) { return holder.lock(); }
69};
70
71} // namespace detail
72/// \endcond
73
74///
75/// Traits template that lets the library hold objects through custom smart pointers.
76///
77/// Specializing HolderTraits teaches the library how to inspect and unwrap your engine's smart
78/// pointers.
79///
80/// Standard smart pointers like `std::shared_ptr` and `std::unique_ptr` work automatically without
81/// extra setup. You only need to specialize this template when integrating custom smart pointers
82/// from your application or engine.
83///
84/// This example specializes HolderTraits for an engine reference-counted pointer and binds a member
85/// function:
86///
87/// ```
88/// template <typename T>
89/// struct ultralight::HolderTraits<MyRef<T>> {
90/// using element_type = T;
91/// static constexpr HolderKind kind = HolderKind::Shared;
92/// static T* Get(const MyRef<T>& holder) { return holder.get(); }
93/// };
94///
95/// MyRef<Player> player = SpawnPlayer();
96/// app["jump"] = js::Bind(player, &Player::Jump);
97/// ```
98///
99/// ## Built-In Holders
100///
101/// The library provides built-in traits for standard and library smart pointers. Built-in owning
102/// holders declare a HolderKind:
103///
104/// | Holder | Kind |
105/// |-------------------|-----------|
106/// | RefPtr | Shared |
107/// | `std::shared_ptr` | Shared |
108/// | `std::unique_ptr` | Exclusive |
109/// | js::Eternal | Shared |
110///
111/// The holder's kind determines how the library manages your object's lifetime:
112///
113/// - **Shared holders share ownership between native code and the library.** Copies keep the object
114/// alive until native code and any JavaScript wrapper, DOM listener, or data binding release it.
115/// - **Exclusive holders transfer unique ownership to the library.** Moving the pointer leaves your
116/// native holder empty, and the library keeps the object alive for as long as the wrapper, DOM
117/// listener, or data binding exists.
118///
119/// WeakPtr and `std::weak_ptr` serve as built-in weak holders by defining Lock() instead of a kind.
120/// A weak holder doesn't keep your object alive-- the library temporarily locks it for each call,
121/// skipping the operation if the object has been destroyed, or throwing an error when called from
122/// JavaScript.
123///
124/// ## Custom Smart Pointers
125///
126/// A single specialization works across the JavaScript, DOM, and data-binding APIs. Specialize the
127/// template as an owning holder or a weak holder, depending on how your pointer manages lifetime:
128///
129/// - **An owning holder defines `element_type`, `kind`, and Get().** Set `kind` to
130/// HolderKind::Shared or HolderKind::Exclusive, and return a raw pointer from Get().
131/// - **A weak holder defines `element_type` and Lock().** Return an owning holder from Lock() to
132/// provide temporary access to the instance during a call.
133///
134/// This example specializes HolderTraits for an engine weak pointer:
135///
136/// ```
137/// template <typename T>
138/// struct ultralight::HolderTraits<MyWeakRef<T>> {
139/// using element_type = T;
140/// static MyRef<T> Lock(const MyWeakRef<T>& holder) { return holder.lock(); }
141/// };
142/// ```
143///
144/// To use your custom owning holder as the canonical holder for a bound class, specialize
145/// js::ClassHolder for that class.
146///
147/// Custom specializations take priority over built-in traits.
148///
149/// @warning Declare your specialization in namespace ultralight before any code passes the pointer
150/// to the library. If the compiler encounters code using the holder before seeing your
151/// specialization, it either falls back silently to built-in traits or fails to build.
152///
153/// @see js::ClassHolder, js::Bind(), dom::LockableHolder, js::Eternal
154///
155template <typename H, typename = void>
156struct HolderTraits : detail::BuiltinHolderTraits<H> {};
157
158/// \cond INTERNAL
159namespace detail {
160
161template <typename H>
162concept HolderHasLock = requires(const H& holder) { HolderTraits<H>::Lock(holder); };
163
164template <typename H>
165concept HolderHasGet = requires(const H& holder) { HolderTraits<H>::Get(holder); };
166
167// Strong access to a holder's object for one use: Lock()'s result for a weak holder, or the
168// pointee itself for an owning holder (which keeps the object alive on its own). The caller
169// tests the result, then dereferences it.
170template <typename H>
171decltype(auto) LockHolder(const H& holder) {
172 if constexpr (HolderHasLock<H>)
173 return HolderTraits<H>::Lock(holder);
174 else
175 return HolderTraits<H>::Get(holder);
176}
177
178} // namespace detail
179/// \endcond
180
181} // namespace ultralight
Root namespace for every public Ultralight type, function, and enumeration.
std::lock_guard< Lock > LockHolder
Definition Lock.h:84
HolderKind
Whether a holder shares ownership of a bound-class instance or owns it alone.
Definition HolderTraits.h:16
@ Exclusive
One holder owns the instance and ownership moves (eg, std::unique_ptr).
Definition HolderTraits.h:26
@ Shared
Copies share ownership (eg, RefPtr), or the instance is never destroyed (js::Eternal).
Definition HolderTraits.h:20
Traits template that lets the library hold objects through custom smart pointers.
Definition HolderTraits.h:156