Module: gears.reactive
Utility module to convert functions to Excel like objects.
When converting a function into a gears.reactive object, all properties accessed by the function are monitored for changes.
When such a change is detected, the property::value
signal is emitted. When
used within a widget declarative construct, the property it is attached
to will also be automatically updated.
Theory
To use this module and, more importantly, to understand how to write something
that actually works based on it, some background is required. Most AwesomeWM
objects are based on the gears.object module or it's C
equivalent. Those
objects have "properties". Behind the scene, they have getters, setters and
a signal to notify the value changed.
gears.reactive adds a firewall like sandbox around the function. It intercept any gears.object instance trying to cross the sandbox boundary and replace them with proxy objects. Those proxies have built-in introspection code to detect how they are used. This is then converted into a list of objects and signals to monitor. Once one of the monitored object emits one of the monitored signal, then the whole function is re-evaluated. Each time the function is evaluated, the "target" properties are updated. The reactive function result or any call to external function from within goes through the firewall again and any proxies are removed.
That design has one big limitation. It cannot detect any changes which are not directly part of gears.object instance. You cannot use random tables and expect the function to be called when it's content change. To work around this, it is recommanded to make "hub" objects to store the data used within the reactive function.
Recommanded usage
The best way to use gears.reactive is when the value used within the expressions are part of other objects. It can be a gears.watcher, but it can also be a custom object:
-- It's important to set 'enable_auto_signals' totrue
or it wont work. -- -- Note that most AwesomeWM objects (and most modules) objects can be -- used directly as long as they implement the signalproperty::
spec. -- -- So you don't *need* a hub object, but it's safer to use one. local my_hub = gears.object { enable_properties = true, enable_auto_signals = true } -- Better set a default value to avoid weirdness. my_hub.some_property = 42 -- This is an example, in practice do this in your -- wibar widget declaration tree. local w = wibox.widget { markup = gears.reactive(function() -- Each timemy_hub.some_property
changes, this will be -- re-interpreted. return '<i>' .. (my_hub.some_property / 100) .. '</i>' end), widget = wibox.widget.textbox } -- This will update the widget text to '<i>13.37</i>' my_hub.some_property = 1337
Limitations
gears.reactive is pushing Lua way beyond what it has been designed for. Because of this, there is some limitations.
- This module will NOT try to track the change of other functions and methods called by the expression. It is NOT recursive and only the top level properties are tracked. This is a feature, not a bug. If it was recursive, this list of limitations or gotchas would be endless.
- This only works with gears.object and Core API objects which implement
the
property::*
signals correctly. If it is a regular Lua table or the property signals are incorrectly used, the value changes cannot be detected. If you find something that should work, but doesn't in one of the AwesomeWM API, please report a bug. - More generally, when making a custom gears.object with custom setters, it is the programmer responsibility to emit the signal. It is also required to only emit those signals when the property actually changes to avoid an unecessary re-evaluations.
- Changing the type of the variables accessed by the reactive function (its "upvalues") after the reactive expression has been created wont be detected. It will cause missed updates and, potentially, hard to debug Lua errors within the proxy code itself.
- Internally, the engine tries its best to prevent the internal proxy objects to leak out the sandbox. However this cannot be perfect, at least not without adding limitation elsewhere. It is probably worth reporting a bug if you encounter such an issue. But set your expectations, not all corner case can be fixed.
- Don't use rawset in the expression.
- If the return value is a table, only the first 3 nesting levels are sanitized. Avoid using nested tables in the returned value if you can. gears.object instances should be fine.
- There is currently no way to disable a reactive expression once it's been defined. This may change eventually.
- Rio Lua 5.1 (not LuaJIT 2.1) is currently not support. If you need it, please report a bug.
Info:
- Copyright: 2017-2020 Emmanuel Lepage-Vallee
-
Originally authored by: Emmanuel Lepage-Vallee <elv1313@gmail.com>
(Full contributors list available on our github project)
Constructors
gears.reactive {[args]} | Create a new gears.reactive object. |
Object properties
expression | function | A function which will be evaluated each time its content changes. | |
value | N/A | The current value of the expression. | |
delayed | boolean | Only evaluate once per event loop iteration. |
Object methods
:disconnect () | Disconnect all expression signals. | |
:refresh () | Recompute the expression. | |
:emit_signal (name, ...) | Emit a signal. | Inherited from gears.object |
:connect_signal (name, func) | Connect to a signal. | Inherited from gears.object |
:weak_connect_signal (name, func) | Connect to a signal weakly. | Inherited from gears.object |
Signals
target_added | Emitted when a new property is attached to this reactive expression. |
Constructors
- gears.reactive {[args]}
-
Create a new gears.reactive object.
Parameters:
- args
- expression function A function which accesses other gears.object.
- object gears.object Any AwesomeWM object.
- property string The name of the property to track.
- args
Object properties
- expression function · 1 signal
-
A function which will be evaluated each time its content changes.
Click to display more Emit signals:
property::gears.reactive.expression
When the gears.reactive.expression value changes.self
gears.reactive The object which changed (useful when connecting many object to the same callback).new_value
function
The new value affected to the property.
- value N/A · 1 signal
-
The current value of the expression.
Click to display more Emit signals:
property::gears.reactive.value
When the gears.reactive.value value changes.self
gears.reactive The object which changed (useful when connecting many object to the same callback).
- delayed boolean · 1 signal
-
Only evaluate once per event loop iteration.
In most case this is a simple performance win, but there is some case where you might want the expression to be evaluated each time one of the upvalue "really" change rather than batch them. This option is enabled by default.
Click to display more Emit signals:
property::gears.reactive.delayed
When the gears.reactive.delayed value changes.self
gears.reactive The object which changed (useful when connecting many object to the same callback).new_value
delayed The new value affected to the property.
Object methods
- :disconnect ()
- Disconnect all expression signals.
- :refresh ()
-
Recompute the expression.
When the expression uses a non-object upvalue, the changes cannot be auto-retected. Calling
:refresh()
will immediatly recompute the expression. - :emit_signal (name, ...) · Inherited from gears.object
-
Emit a signal.
Parameters:
- name string The name of the signal.
- ... Extra arguments for the callback functions. Each connected function receives the object as first argument and then any extra arguments that are given to emit_signal().
- :connect_signal (name, func) · Inherited from gears.object
-
Connect to a signal.
Parameters:
- name string The name of the signal.
- func function The callback to call when the signal is emitted.
- :weak_connect_signal (name, func) · Inherited from gears.object
-
Connect to a signal weakly.
This allows the callback function to be garbage collected and automatically disconnects the signal when that happens.
Warning: Only use this function if you really, really, really know what you are doing.
Parameters:
- name string The name of the signal.
- func function The callback to call when the signal is emitted.
Signals
- target_added
-
Emitted when a new property is attached to this reactive expression.
Arguments:
- object gears.object The object (often the widget).
- property string The property name.