docs
Loading...
Searching...
No Matches
Holder.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
7#include <Ultralight/RefPtr.h>
8
9#include <concepts>
10#include <memory>
11#include <type_traits>
12
13namespace ultralight {
14namespace js {
15
16///
17/// Non-owning holder for bound-class singletons and program-lifetime objects.
18///
19/// An Eternal wraps a pointer to a native object that lives for the entire run of the application
20/// and is never destroyed, such as an engine subsystem or a global singleton. It lets page scripts
21/// interact with the native instance through a standard JavaScript wrapper without reference
22/// counting or ownership transfers.
23///
24/// You'll typically specialize js::ClassHolder with Eternal beside the class declaration so every
25/// file that binds or converts the class includes it. This makes Eternal the class's canonical
26/// holder, allowing you to assign the singleton directly to an API path.
27///
28/// This example exposes a game's world singleton to the page as `app.world`:
29///
30/// ```
31/// class GameWorld {
32/// public:
33/// static GameWorld& Current();
34/// };
35///
36/// template <>
37/// struct ultralight::js::ClassHolder<GameWorld> {
38/// using type = js::Eternal<GameWorld>;
39/// };
40///
41/// void RegisterWorld(js::API& api) {
42/// api.DefineClass<GameWorld>("GameWorld"); // no Constructor: `new` throws
43/// api["world"] = js::Eternal(&GameWorld::Current());
44/// }
45/// ```
46///
47/// ## Differences from Raw Pointers
48///
49/// Both a `T*` and an Eternal leave ownership with you, but they differ in how they can cross the
50/// bridge:
51///
52/// - **Raw pointers cannot be assigned to an API path directly.** A bound function or property
53/// getter can return a `T*` to give the page a wrapper that lasts, but raw pointers can't be
54/// passed to API::Emit() or Resolver::Resolve().
55/// - **Eternal holders support direct assignment and deferred deliveries.** An Eternal can be
56/// assigned directly to an API path (as shown above) or passed to API::Emit() and
57/// Resolver::Resolve(), which deliver the instance on a later frame during Renderer::Update().
58///
59/// @note ClassBuilder::Constructor() doesn't support classes whose canonical holder is an Eternal.
60/// Calling `new` on the page throws a TypeError with code `ULJS_NO_CTOR` (use a property
61/// binding or a static method returning the Eternal instead).
62///
63/// @see js::ClassHolder, js::ClassBuilder, ultralight::HolderTraits
64///
65template <typename T>
66class Eternal {
67 public:
68 ///
69 /// Create an empty Eternal.
70 ///
71 constexpr Eternal() = default;
72
73 ///
74 /// Create an Eternal for an instance.
75 ///
76 /// @param instance The instance, which must never be destroyed.
77 ///
78 constexpr explicit Eternal(T* instance) : ptr_(instance) {}
79
80 ///
81 /// Get the instance (NULL when empty).
82 ///
83 T* get() const { return ptr_; }
84
85 ///
86 /// Whether or not this holds an instance.
87 ///
88 explicit operator bool() const { return ptr_ != nullptr; }
89
90 private:
91 T* ptr_ = nullptr;
92};
93
94template <typename T>
96
97///
98/// Whether or not H (ignoring const and references) is an owning holder: its
99/// ultralight::HolderTraits declares element_type, kind, and Get().
100///
101template <typename H>
102concept Holder = requires(const std::remove_cvref_t<H>& h) {
103 typename HolderTraits<std::remove_cvref_t<H>>::element_type;
104 { HolderTraits<std::remove_cvref_t<H>>::kind } -> std::convertible_to<HolderKind>;
106};
107
108///
109/// Whether or not H (ignoring const and references) is a weak holder: its
110/// ultralight::HolderTraits declares element_type and a Lock() that returns an owning holder.
111///
112template <typename H>
113concept LockableHolder = requires(const std::remove_cvref_t<H>& h) {
114 typename HolderTraits<std::remove_cvref_t<H>>::element_type;
116};
117
118///
119/// Type trait that selects the smart pointer type for owned instances of a bound class.
120///
121/// When an owned instance of a bound C++ class crosses the bridge to JavaScript, it travels in its
122/// canonical holder-- the smart pointer that manages native ownership for that class.
123///
124/// This specialization sets `std::shared_ptr` as the holder for a bound `Database` class:
125///
126/// ```
127/// template <>
128/// struct ultralight::js::ClassHolder<Database> {
129/// using type = std::shared_ptr<Database>;
130/// };
131/// ```
132///
133/// ## Where the Holder Is Used
134///
135/// The library requires the canonical holder when transferring ownership across the bridge:
136///
137/// - **Bound functions must return owned instances in the canonical holder.** Returning an owned
138/// instance in any other smart pointer is a compile error.
139/// - **API::Emit() and Resolver::Resolve() require the canonical holder.** Delivering an instance
140/// later during Renderer::Update() requires an owning smart pointer rather than a borrowed raw
141/// pointer.
142///
143/// ## Supported Holder Types
144///
145/// By default, `type` is RefPtr for a class that derives from RefCounted, or std::unique_ptr for
146/// any other class. You can specialize ClassHolder to select any smart pointer that matches your
147/// ownership model:
148///
149/// - **Use std::shared_ptr for shared ownership.** Native code and the JavaScript wrapper share
150/// ownership of non-RefCounted objects.
151/// - **Use js::Eternal for program-lifetime objects.** Singletons and subsystems wrapped in
152/// js::Eternal are never destroyed by the library or the page.
153/// - **Use a custom smart pointer with HolderTraits.** Specialize ultralight::HolderTraits for your
154/// smart pointer type before specializing ClassHolder.
155///
156/// \parblock
157/// @note Declare the specialization in the header that defines the class, so every file that binds
158/// or converts the class uses the same holder type.
159/// \endparblock
160///
161/// \parblock
162/// @note ClassBuilder::Constructor() works only when the canonical holder is RefPtr,
163/// std::unique_ptr, or std::shared_ptr. For another holder (such as js::Eternal), bind a
164/// static factory method with ClassBuilder::StaticMethod() that returns the holder instead.
165/// \endparblock
166///
167/// @see js::ClassBuilder, ultralight::HolderTraits, js::Eternal, js::Detach()
168///
169template <typename T, typename = void>
171 using type = std::conditional_t<std::is_base_of_v<RefCounted, T>, RefPtr<T>,
172 std::unique_ptr<T>>;
173};
174
175} // namespace js
176
177/// \cond INTERNAL
178namespace detail {
179
180template <typename T>
181struct BuiltinHolderTraits<js::Eternal<T>> {
182 using element_type = T;
183 static constexpr HolderKind kind = HolderKind::Shared;
184 static T* Get(const js::Eternal<T>& holder) { return holder.get(); }
185};
186
187} // namespace detail
188/// \endcond
189
190} // namespace ultralight
Lightweight mutex optimized for short locking periods.
Definition Lock.h:23
A nullable smart pointer.
Definition RefPtr.h:126
Non-owning holder for bound-class singletons and program-lifetime objects.
Definition Holder.h:66
constexpr Eternal(T *instance)
Create an Eternal for an instance.
Definition Holder.h:78
constexpr Eternal()=default
Create an empty Eternal.
T * get() const
Get the instance (NULL when empty).
Definition Holder.h:83
Whether or not H (ignoring const and references) is an owning holder: its ultralight::HolderTraits de...
Definition Holder.h:102
Whether or not H (ignoring const and references) is a weak holder: its ultralight::HolderTraits decla...
Definition Holder.h:113
Type-checked JavaScript bridge between C++ and web pages.
Definition JSInterop.h:141
Root namespace for every public Ultralight type, function, and enumeration.
HolderKind
Whether a holder shares ownership of a bound-class instance or owns it alone.
Definition HolderTraits.h:16
@ 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
Type trait that selects the smart pointer type for owned instances of a bound class.
Definition Holder.h:170
std::conditional_t< std::is_base_of_v< RefCounted, T >, RefPtr< T >, std::unique_ptr< T > > type
Definition Holder.h:171