Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
d702696
Derive the frozen element key from the render count
maartenbreddels Sep 26, 2026
934b919
Keep the shared ComponentWidget on its widget class
maartenbreddels Sep 26, 2026
9ea7e1d
Make the generated element factories reuse one component per class
maartenbreddels Sep 26, 2026
4ea37e3
Move the element building blocks into a module that Cython can compile
maartenbreddels Sep 26, 2026
1c8f39d
Mount new components without the bookkeeping of the two phase walk
maartenbreddels Sep 26, 2026
557213e
Compile the mount, the hooks and the listeners of the fast renderer
maartenbreddels Sep 26, 2026
9264151
Test solara's writes to the mount globals, and remove without a lookup
maartenbreddels Sep 26, 2026
b21f166
Do less per widget and per removed component in the compiled mount
maartenbreddels Sep 26, 2026
3ad6b6d
Keep one copy of the hooks and of the component body call
maartenbreddels Sep 26, 2026
926d1a8
Describe the mount and the optional compiled core in the benchmark RE…
maartenbreddels Sep 26, 2026
f2866c8
Give use_memo a Python frame when reacton is compiled
maartenbreddels Sep 26, 2026
5e861d3
Make the first render and each mounted component cheaper
maartenbreddels Sep 26, 2026
1ba6ca8
Do less per widget and per removed component of a mount
maartenbreddels Sep 26, 2026
46dcb72
Make less for a new render context and visit fewer contexts after a m…
maartenbreddels Sep 26, 2026
0516be3
Mention the compiled first render in the benchmark README
maartenbreddels Sep 26, 2026
51c6f9c
Declare _RerenderReason before the function that makes it global
maartenbreddels Sep 26, 2026
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,8 @@ examples
site
_site
.mypy_cache

# optional Cython build (setup_cython.py)
/build/
/reacton/_fastcore.c
/reacton/_fastcore.html
26 changes: 22 additions & 4 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,26 @@ renderer (`_RenderContextFast`, `REACTON_FAST=1`) addresses both:
identical child widget objects (`_values_identical`), avoiding pointless
traitlets assignments. Container widgets keep the kwargs they were last
created or updated with (`resolved_kwargs`) for this compare.
- **Mount path.** The implicit container is only built when a component body
returns `None`; component context managers are entered without an
`ExitStack`; the child visitors dispatch on exact types and skip scalars.
- **Mount path.** A new component (first render, new list item, another
component type at a key) is mounted in one walk (`_fastcore.mount_component`):
bodies run as in the render phase, widgets are made children first. A mounted
component (`_MountedContext`) keeps its element tree positionally (`nodes`:
the widgets and child contexts in the order they were made); the dicts of the
update paths (`elements`, `widgets`, `children`, `element_to_widget`,
`used_keys`, `resolved_kwargs`, ...) are made from it, with the same keys, when
they are first used (`_fastcore.materialize`), and a mounted subtree that goes
away is removed from it (`_fastcore.remove_mounted`). A pass that cannot keep
its mounts (state set or an exception during the mount, shared elements, a
widget that fails to be made) undoes them into the two phase bookkeeping. The
implicit container is only built when a component body returns `None`.
- **Compiled core (optional).** `reacton/_fastcore.py` holds the element base
classes, `render_fixed` and the first render of a new render context
(`render_first`: it mounts the root without the walks of `render()`, whose loop
takes over when more passes are needed), the mount, the hooks and the
listener/setter objects. It is plain
Python; `python setup_cython.py build_ext --inplace` compiles it with Cython
(pure Python mode, types in `_fastcore.pxd`). `REACTON_CYTHON=0` forces the
plain version when a compiled one is present.
- **Side-effect ("orphan") widgets** (Layout/Style created during construction)
are tracked via ipywidgets' `on_widget_constructed` hook instead of diffing
the global widgets dict per creation — the old diff was O(live widgets) per
Expand Down Expand Up @@ -107,7 +124,8 @@ random state changes and compares the widgets and the effect order.
**Keys.** `el._key` or a positional default. A context's root key is `"/"`.
Children of an element with key `K`: list → `f"{K}{i}/"`, dict → `f"{K}{k}/"`.
Component child contexts live in `context.children[key]`. A duplicate key in one
context raises `KeyError`. `el._key_frozen` is set once an element is rendered.
context raises `KeyError`. `el._key_frozen` is true once an element is rendered
(`el._render_count > 0`).

**Render phase, per element:**
- `el._render_count += 1` (a shared element is visited once; a non-shared one
Expand Down
230 changes: 230 additions & 0 deletions reacton/_fastcore.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# Cython declarations for _fastcore.py (pure Python mode). Everything that makes the compiled
# module fast is declared here, so the .py runs as plain Python without paying for it.
#
# Do NOT declare these module globals here (keep them plain module globals): reacton.core
# assigns them from Python when solara writes reacton.core._default_container and
# reacton.core._component_context_manager_classes (see reacton.core._CoreModule), and
# reacton.core.DEBUG; and reacton.core writes _provides and _log_debug.
cimport cython
from libc.stdlib cimport getenv
from cpython.object cimport PyTypeObject
from cpython.ref cimport PyObject


cdef tuple _EMPTY_TUPLE = ()


cdef inline object _object_new(object cls):
# object.__new__(cls), directly (cls must not override __new__)
return (<PyTypeObject*>cls).tp_new(cls, <PyObject*>_EMPTY_TUPLE, NULL)


cdef inline bint _getenv_fast():
# REACTON_FAST=1
cdef const char* value = getenv(b"REACTON_FAST")
return value != NULL and value[0] == 49 and value[1] == 0


cdef class ContainerAdder:
cdef dict __dict__
cdef object __weakref__
cdef public object el, prop_name
cdef public list created


# (no __dict__ and __weakref__ here: the Python subclasses in reacton.core get them, and make
# the dict when an attribute is first set; a dict declared here is made for every element)
cdef class ElementBase:
cdef public object component
cdef public object args
cdef public object kwargs
cdef public object mime_bundle
cdef public object _key
cdef public object _meta
cdef public object _on_kwargs
cdef public object _leaf
cdef public object _event_handlers
cdef public bint is_shared
cdef public Py_ssize_t _render_count


cdef class ValueElementBase(ElementBase):
cdef public object value_property


# ---- the mount (reacton.core._RenderContextFast): contexts and the render context are plain
# Python objects (the update paths use them from Python), elements are typed

cdef class _Mount:
cdef public object rc
cdef public list recording
cdef public list order
cdef public bint failed
cdef public Py_ssize_t raised
cdef public set shared_next
cdef public dict keys
cdef public list adders
cdef public ContainerAdder body_adder
cdef public object top


cdef object _new_instance(object cls)

# (only _fastcore uses these)
cdef dict _plain_classes
cdef dict _logger_cache
cdef frozenset _SCALAR_TYPES

cdef int _plain_class(object cls) except -1
cdef class _WidgetInfo:
cdef public object widget
cdef public frozenset trait_names
cdef public object batched

@cython.locals(info=_WidgetInfo)
cdef _WidgetInfo _widget_info(object component)

@cython.locals(m=_Mount, c=object, widget=object)
cpdef object mount_component(object rc, ElementBase el, object key, object parent_context, object order, object context)

cdef object _new_context(object parent)
cdef object _adopt(object precreated, object parent)

@cython.locals(rc=object, precreated_children=object, nodes=list, managers=object, raised=Py_ssize_t, provides=Py_ssize_t, root=object, widget=object, adders=list, user_contexts=object)
cdef object _mount_component(_Mount m, ElementBase el, object parent, list parent_nodes, object context, object key)

@cython.locals(root=object)
cdef object _call_body(_Mount m, ElementBase el, object managers)

@cython.locals(component=object, default_container=object, created=list, root_element=object, kwargs=dict, container=object)
cpdef object call_component(list container_adders, ContainerAdder adder, ElementBase el)

@cython.locals(key=object, all_keys=dict, keys=set, component=object, precreated=object, precreated_children=object, child=object, widget=object, kwargs=dict, resolved=dict, name=object, value=object, new_value=object, rc=object, element_class=object, plain=int, added=object, listener=object, info=_WidgetInfo, recording=list, count=Py_ssize_t, listeners=dict, traits=frozenset, callback=object, widget_class=object, handlers=tuple, handler=object, orphan_ids=object, widgets_dict=object, maybe_listener=bint, t=object)
cdef object _mount_node(_Mount m, ElementBase el, object c, list nodes, object dkey)

@cython.locals(values=list, index=Py_ssize_t, x=object, w=object)
cdef list _mount_list(_Mount m, object value, object c, list nodes, object dkey)

cdef object _mount_value(_Mount m, object value, object c, list nodes, object dkey)

cpdef object init_context(object c)

@cython.locals(root=object)
cpdef object init_render_context(object rc, object element, object container, object children_trait, object handle_error, bint fast)

@cython.locals(reasons=object)
cpdef add_rerender_reason(object rc, object reason)

cpdef bint fast_selected()

@cython.locals(enabled=object)
cdef object _info_enabled()

@cython.locals(rc=object, widget=object)
cpdef object render_fixed(object element, object handle_error=*)

@cython.locals(root=object, lock=object, widget=object, prev_rc=object, key=object, more=bint)
cpdef object render_first(object rc, object element, object container)

@cython.locals(order=list, raised=bint, context=object, effects=object, parent=object, effect=object, widget=object, el=ElementBase, key=object)
cpdef object finish_mount(object rc, object root)

@cython.locals(node=object, parent=object)
cdef object _bubble_exceptions(object c)

@cython.locals(nodes=list, w=_Materialize, root=object)
cpdef object materialize(object c)

cdef class _Materialize:
cdef public bint partial
cdef public list nodes
cdef public Py_ssize_t index
cdef public Py_ssize_t order
cdef public dict elements
cdef public dict widgets
cdef public dict children
cdef public dict element_to_widget
cdef public set used_keys
cdef public dict resolved_kwargs

@cython.locals(key=object, component=object, child=object, widget=object, resolved=dict, start=Py_ssize_t, name=object, value=object, new_value=object, node=object, traits=frozenset, callback_wrappers=dict, listener=object, added=object)
cpdef object node(self, ElementBase el, object default_key)

@cython.locals(t=object, values=list, index=Py_ssize_t, x=object, w=object)
cpdef object value(self, object value, object key)


@cython.locals(el=object, widget=object, added=object, mounted_listeners=bint, listener=object, orphans=object, orphan=object, orphan_widget=object, close=object, widgets_dict=object)
cdef object _close_widget_node(object rc, object node)

@cython.locals(errors=list)
cpdef object remove_mounted(object rc, object child_context, bint closing)

@cython.locals(context=object, effect=object, cleanup=object, handler=object, nodes=list, node=object, switched=bint, effects=object, handlers=object, errors=list, errors_children=list, sub=list)
cdef list _remove_mounted(object rc, object child_context, bint closing)


# ---- the hooks

cdef class RefBase:
cdef public object current


cdef class _EventHandler:
cdef public object rc, context, callback, event, widget, registered_event
cdef public bint removed


@cython.locals(rc=object)
cpdef use_state(initial, key=*, eq=*)

@cython.locals(context=object, index=Py_ssize_t, state=dict, value=object, setters=dict, setter=object, eq_cell=list)
cpdef rc_use_state(rc, initial, key, eq)

cpdef rc_use_ref(rc, initial_value)

@cython.locals(rc=object)
cpdef use_ref(initial_value)

@cython.locals(memo=object, index=Py_ssize_t, value=object, dependencies_previous=object)
cdef object _use_ref(object context, object initial_value)

@cython.locals(rc=object)
cpdef use_memo(f, dependencies=*, debug_name=*)

@cython.locals(context=object, name=object, memo=object, index=Py_ssize_t, value=object, entry=tuple, dependencies_previous=object)
cpdef rc_use_memo(rc, f, dependencies, debug_name)

@cython.locals(rc=object)
cpdef use_effect(effect, dependencies=*)

@cython.locals(new=object)
cdef object _new_effect(object callable, object dependencies)

@cython.locals(rc=object, context=object, value=object, user_contexts=object)
cpdef use_context(user_context)

cdef class _ContextListener:
cdef public object set_counter

cdef class _ContextConnect:
cdef public object context, user_context, listener

@cython.locals(context=object, effects=object, index=Py_ssize_t, previous_effect=object)
cpdef rc_use_effect(rc, effect, dependencies)

@cython.locals(vue=object)
cdef object _is_vue(object component)

@cython.locals(rc=object, context=object, ref=object, handler=object, component=object, events=object, handlers=tuple)
cpdef use_event(el, event_and_modifiers, callback)


cdef class _Listener:
cdef public object rc, context, name, widget, callback


cdef class _Setter:
cdef public object rc, context, key
cdef public object eq
cdef public object created_stack
Loading
Loading