fix: local invalidation chains w/extract fixes
parent
e889d20284
commit
035d8971f3
|
@ -14,7 +14,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""Declares `ExtractDataNode`."""
|
"""Implements `ExtractDataNode`."""
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
@ -40,16 +40,12 @@ TDMonitorData: typ.TypeAlias = td.components.data.monitor_data.MonitorData
|
||||||
class ExtractDataNode(base.MaxwellSimNode):
|
class ExtractDataNode(base.MaxwellSimNode):
|
||||||
"""Extract data from sockets for further analysis.
|
"""Extract data from sockets for further analysis.
|
||||||
|
|
||||||
# Socket Sets
|
Socket Sets:
|
||||||
## Sim Data
|
Sim Data: Extract monitor data from simulation data by-name.
|
||||||
Extracts monitors from a `MaxwelFDTDSimDataSocket`.
|
Monitor Data: Extract `Expr`s from monitor data by-component.
|
||||||
|
|
||||||
## Monitor Data
|
|
||||||
Extracts array attributes from a `MaxwelFDTDSimDataSocket`.
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
extract_filter: Identifier for data to extract from the input.
|
extract_filter: Identifier for data to extract from the input.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
node_type = ct.NodeType.ExtractData
|
node_type = ct.NodeType.ExtractData
|
||||||
|
@ -71,12 +67,18 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
extract_filter: enum.StrEnum = bl_cache.BLField(
|
extract_filter: enum.StrEnum = bl_cache.BLField(
|
||||||
enum_cb=lambda self, _: self.search_extract_filters(),
|
enum_cb=lambda self, _: self.search_extract_filters(),
|
||||||
|
cb_depends_on={'sim_data_monitor_nametype', 'monitor_data_type'},
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Computed: Sim Data
|
# - Computed: Sim Data
|
||||||
####################
|
####################
|
||||||
@property
|
@events.on_value_changed(socket_name='Sim Data')
|
||||||
|
def on_sim_data_changed(self) -> None: # noqa: D102
|
||||||
|
log.critical('On Value Changed: Sim Data')
|
||||||
|
self.sim_data = bl_cache.Signal.InvalidateCache
|
||||||
|
|
||||||
|
@bl_cache.cached_bl_property()
|
||||||
def sim_data(self) -> td.SimulationData | None:
|
def sim_data(self) -> td.SimulationData | None:
|
||||||
"""Extracts the simulation data from the input socket.
|
"""Extracts the simulation data from the input socket.
|
||||||
|
|
||||||
|
@ -92,7 +94,7 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@bl_cache.cached_bl_property()
|
@bl_cache.cached_bl_property(depends_on={'sim_data'})
|
||||||
def sim_data_monitor_nametype(self) -> dict[str, str] | None:
|
def sim_data_monitor_nametype(self) -> dict[str, str] | None:
|
||||||
"""For simulation data, deduces a map from the monitor name to the monitor "type".
|
"""For simulation data, deduces a map from the monitor name to the monitor "type".
|
||||||
|
|
||||||
|
@ -110,7 +112,12 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Computed Properties: Monitor Data
|
# - Computed Properties: Monitor Data
|
||||||
####################
|
####################
|
||||||
@property
|
@events.on_value_changed(socket_name='Monitor Data')
|
||||||
|
def on_monitor_data_changed(self) -> None: # noqa: D102
|
||||||
|
log.critical('On Value Changed: Sim Data')
|
||||||
|
self.monitor_data = bl_cache.Signal.InvalidateCache
|
||||||
|
|
||||||
|
@bl_cache.cached_bl_property()
|
||||||
def monitor_data(self) -> TDMonitorData | None:
|
def monitor_data(self) -> TDMonitorData | None:
|
||||||
"""Extracts the monitor data from the input socket.
|
"""Extracts the monitor data from the input socket.
|
||||||
|
|
||||||
|
@ -126,7 +133,7 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@bl_cache.cached_bl_property()
|
@bl_cache.cached_bl_property(depends_on={'monitor_data'})
|
||||||
def monitor_data_type(self) -> str | None:
|
def monitor_data_type(self) -> str | None:
|
||||||
r"""For monitor data, deduces the monitor "type".
|
r"""For monitor data, deduces the monitor "type".
|
||||||
|
|
||||||
|
@ -149,7 +156,7 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@bl_cache.cached_bl_property()
|
@bl_cache.cached_bl_property(depends_on={'monitor_data_type'})
|
||||||
def monitor_data_attrs(self) -> list[str] | None:
|
def monitor_data_attrs(self) -> list[str] | None:
|
||||||
r"""For monitor data, deduces the valid data-containing attributes.
|
r"""For monitor data, deduces the valid data-containing attributes.
|
||||||
|
|
||||||
|
@ -304,26 +311,9 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
col.prop(self, self.blfields['extract_filter'], text='')
|
col.prop(self, self.blfields['extract_filter'], text='')
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Events
|
# - FlowKind.Value: Sim Data -> Monitor Data
|
||||||
####################
|
|
||||||
@events.on_value_changed(
|
|
||||||
# Trigger
|
|
||||||
socket_name={'Sim Data', 'Monitor Data'},
|
|
||||||
prop_name='active_socket_set',
|
|
||||||
run_on_init=True,
|
|
||||||
)
|
|
||||||
def on_input_sockets_changed(self) -> None:
|
|
||||||
"""Invalidate the cached properties for sim data / monitor data, and reset the extraction filter."""
|
|
||||||
self.sim_data_monitor_nametype = bl_cache.Signal.InvalidateCache
|
|
||||||
self.monitor_data_type = bl_cache.Signal.InvalidateCache
|
|
||||||
self.monitor_data_attrs = bl_cache.Signal.InvalidateCache
|
|
||||||
self.extract_filter = bl_cache.Signal.ResetEnumItems
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Output (Value): Sim Data -> Monitor Data
|
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
# Trigger
|
|
||||||
'Monitor Data',
|
'Monitor Data',
|
||||||
kind=ct.FlowKind.Value,
|
kind=ct.FlowKind.Value,
|
||||||
# Loaded
|
# Loaded
|
||||||
|
@ -348,10 +338,9 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
return ct.FlowSignal.FlowPending
|
return ct.FlowSignal.FlowPending
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output (Array): Monitor Data -> Expr
|
# - FlowKind.Array|LazyValueFunc: Monitor Data -> Expr
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
# Trigger
|
|
||||||
'Expr',
|
'Expr',
|
||||||
kind=ct.FlowKind.Array,
|
kind=ct.FlowKind.Array,
|
||||||
# Loaded
|
# Loaded
|
||||||
|
@ -407,7 +396,7 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
return ct.FlowSignal.FlowPending
|
return ct.FlowSignal.FlowPending
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Auxiliary (Params): Monitor Data -> Expr
|
# - FlowKind.Params: Monitor Data -> Expr
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Expr',
|
'Expr',
|
||||||
|
@ -422,10 +411,9 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
return ct.ParamsFlow()
|
return ct.ParamsFlow()
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Auxiliary (Info): Monitor Data -> Expr
|
# - FlowKind.Info: Monitor Data -> Expr
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
# Trigger
|
|
||||||
'Expr',
|
'Expr',
|
||||||
kind=ct.FlowKind.Info,
|
kind=ct.FlowKind.Info,
|
||||||
# Loaded
|
# Loaded
|
||||||
|
|
|
@ -416,14 +416,46 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance):
|
||||||
|
|
||||||
# Remove Sockets
|
# Remove Sockets
|
||||||
for bl_socket in bl_sockets_to_remove:
|
for bl_socket in bl_sockets_to_remove:
|
||||||
|
bl_socket_name = bl_socket.name
|
||||||
|
|
||||||
|
# 1. Report the socket removal to the NodeTree.
|
||||||
|
## -> The NodeLinkCache needs to be adjusted manually.
|
||||||
node_tree.on_node_socket_removed(bl_socket)
|
node_tree.on_node_socket_removed(bl_socket)
|
||||||
|
|
||||||
|
# 2. Invalidate the input socket cache across all kinds.
|
||||||
|
## -> Prevents phantom values from remaining available.
|
||||||
self._compute_input.invalidate(
|
self._compute_input.invalidate(
|
||||||
input_socket_name=bl_socket.name,
|
input_socket_name=bl_socket_name,
|
||||||
kind=...,
|
kind=...,
|
||||||
unit_system=...,
|
unit_system=...,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 3. Perform the removal using Blender's API.
|
||||||
|
## -> Actually removes the socket.
|
||||||
all_bl_sockets.remove(bl_socket)
|
all_bl_sockets.remove(bl_socket)
|
||||||
|
|
||||||
|
if direc == 'input':
|
||||||
|
# 4. Run all trigger-only `on_value_changed` callbacks.
|
||||||
|
## -> Runs any event methods that relied on the socket.
|
||||||
|
## -> Only methods that don't **require** the socket.
|
||||||
|
## Trigger-Only: If method loads no socket data, it runs.
|
||||||
|
## `optional`: If method optional-loads socket, it runs.
|
||||||
|
triggered_event_methods = [
|
||||||
|
event_method
|
||||||
|
for event_method in self.filtered_event_methods_by_event(
|
||||||
|
ct.FlowEvent.DataChanged, (bl_socket_name, None, None)
|
||||||
|
)
|
||||||
|
if bl_socket_name
|
||||||
|
not in event_method.callback_info.must_load_sockets
|
||||||
|
]
|
||||||
|
for event_method in triggered_event_methods:
|
||||||
|
log.critical(
|
||||||
|
'%s: Running %s',
|
||||||
|
self.sim_node_name,
|
||||||
|
str(event_method),
|
||||||
|
)
|
||||||
|
event_method(self)
|
||||||
|
|
||||||
def _add_new_active_sockets(self):
|
def _add_new_active_sockets(self):
|
||||||
"""Add and initialize all "active" sockets that aren't on the node.
|
"""Add and initialize all "active" sockets that aren't on the node.
|
||||||
|
|
||||||
|
@ -737,14 +769,14 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance):
|
||||||
socket_name: The input socket that was altered, if any, in order to trigger this event.
|
socket_name: The input socket that was altered, if any, in order to trigger this event.
|
||||||
pop_name: The property that was altered, if any, in order to trigger this event.
|
pop_name: The property that was altered, if any, in order to trigger this event.
|
||||||
"""
|
"""
|
||||||
# log.debug(
|
log.debug(
|
||||||
# '%s: Triggered Event %s (socket_name=%s, socket_kinds=%s, prop_name=%s)',
|
'%s: Triggered Event %s (socket_name=%s, socket_kinds=%s, prop_name=%s)',
|
||||||
# self.sim_node_name,
|
self.sim_node_name,
|
||||||
# event,
|
event,
|
||||||
# str(socket_name),
|
str(socket_name),
|
||||||
# str(socket_kinds),
|
str(socket_kinds),
|
||||||
# str(prop_name),
|
str(prop_name),
|
||||||
# )
|
)
|
||||||
# Outflow Socket Kinds
|
# Outflow Socket Kinds
|
||||||
## -> Something has happened!
|
## -> Something has happened!
|
||||||
## -> The effect is yet to be determined...
|
## -> The effect is yet to be determined...
|
||||||
|
@ -860,12 +892,18 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance):
|
||||||
Parameters:
|
Parameters:
|
||||||
prop_name: The name of the property that changed.
|
prop_name: The name of the property that changed.
|
||||||
"""
|
"""
|
||||||
|
# All Attributes: Trigger Event
|
||||||
|
## -> This declares that the single property has changed.
|
||||||
|
## -> This should happen first, in case dependents need a cache.
|
||||||
if hasattr(self, prop_name):
|
if hasattr(self, prop_name):
|
||||||
# Trigger Event
|
|
||||||
self.trigger_event(ct.FlowEvent.DataChanged, prop_name=prop_name)
|
self.trigger_event(ct.FlowEvent.DataChanged, prop_name=prop_name)
|
||||||
else:
|
|
||||||
msg = f'Property {prop_name} not defined on node {self}'
|
# BLField Attributes: Invalidate BLField Dependents
|
||||||
raise RuntimeError(msg)
|
## -> Dependent props will generally also trigger on_prop_changed.
|
||||||
|
## -> The recursion ends with the depschain.
|
||||||
|
## -> WARNING: The chain is not checked for ex. cycles.
|
||||||
|
if prop_name in self.blfields:
|
||||||
|
self.invalidate_blfield_deps(prop_name)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI Methods
|
# - UI Methods
|
||||||
|
|
|
@ -38,6 +38,7 @@ class InfoDataChanged:
|
||||||
on_changed_sockets: set[ct.SocketName]
|
on_changed_sockets: set[ct.SocketName]
|
||||||
on_changed_props: set[str]
|
on_changed_props: set[str]
|
||||||
on_any_changed_loose_input: set[str]
|
on_any_changed_loose_input: set[str]
|
||||||
|
must_load_sockets: set[str]
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(kw_only=True, frozen=True)
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
||||||
|
@ -356,12 +357,19 @@ def on_value_changed(
|
||||||
return event_decorator(
|
return event_decorator(
|
||||||
event=ct.FlowEvent.DataChanged,
|
event=ct.FlowEvent.DataChanged,
|
||||||
callback_info=InfoDataChanged(
|
callback_info=InfoDataChanged(
|
||||||
|
# Triggers
|
||||||
run_on_init=run_on_init,
|
run_on_init=run_on_init,
|
||||||
on_changed_sockets=(
|
on_changed_sockets=(
|
||||||
socket_name if isinstance(socket_name, set) else {socket_name}
|
socket_name if isinstance(socket_name, set) else {socket_name}
|
||||||
),
|
),
|
||||||
on_changed_props=(prop_name if isinstance(prop_name, set) else {prop_name}),
|
on_changed_props=(prop_name if isinstance(prop_name, set) else {prop_name}),
|
||||||
on_any_changed_loose_input=any_loose_input_socket,
|
on_any_changed_loose_input=any_loose_input_socket,
|
||||||
|
# Loaded
|
||||||
|
must_load_sockets={
|
||||||
|
socket_name
|
||||||
|
for socket_name in kwargs.get('input_sockets', {})
|
||||||
|
if socket_name not in kwargs.get('input_sockets_optional', {})
|
||||||
|
},
|
||||||
),
|
),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
|
@ -200,32 +200,37 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
|
||||||
Contrary to `node.on_prop_changed()`, socket-specific callbacks are baked into this function:
|
Contrary to `node.on_prop_changed()`, socket-specific callbacks are baked into this function:
|
||||||
|
|
||||||
- **Active Kind** (`self.active_kind`): Sets the socket shape to reflect the active `FlowKind`.
|
- **Active Kind** (`self.active_kind`): Sets the socket shape to reflect the active `FlowKind`.
|
||||||
|
**MAY NOT** rely on `FlowEvent` driven caches.
|
||||||
|
- **Overrided Local Events** (`self.active_kind`): Sets the socket shape to reflect the active `FlowKind`.
|
||||||
|
**MAY NOT** rely on `FlowEvent` driven caches.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
prop_name: The name of the property that was changed.
|
prop_name: The name of the property that was changed.
|
||||||
"""
|
"""
|
||||||
## TODO: Evaluate this properly
|
# All Attributes: Trigger Local Event
|
||||||
if self.is_initializing:
|
## -> While initializing, only `DataChanged` won't trigger.
|
||||||
pass
|
if hasattr(self, prop_name):
|
||||||
# log.debug(
|
|
||||||
# '%s: Rejected on_prop_changed("%s") while initializing',
|
|
||||||
# self.bl_label,
|
|
||||||
# prop_name,
|
|
||||||
# )
|
|
||||||
elif hasattr(self, prop_name):
|
|
||||||
# Property Callbacks: Active Kind
|
# Property Callbacks: Active Kind
|
||||||
|
## -> WARNING: May NOT rely on flow.
|
||||||
if prop_name == 'active_kind':
|
if prop_name == 'active_kind':
|
||||||
self.on_active_kind_changed()
|
self.on_active_kind_changed()
|
||||||
|
|
||||||
# Property Callbacks: Per-Socket
|
# Property Callbacks: Per-Socket
|
||||||
|
## -> WARNING: May NOT rely on flow.
|
||||||
self.on_socket_prop_changed(prop_name)
|
self.on_socket_prop_changed(prop_name)
|
||||||
|
|
||||||
# Trigger Event
|
# Not Initializing: Trigger Event
|
||||||
self.trigger_event(ct.FlowEvent.DataChanged)
|
## -> This declares that the socket has changed.
|
||||||
|
## -> This should happen first, in case dependents need a cache.
|
||||||
|
if not self.is_initializing:
|
||||||
|
self.trigger_event(ct.FlowEvent.DataChanged)
|
||||||
|
|
||||||
else:
|
# BLField Attributes: Invalidate BLField Dependents
|
||||||
msg = f'Property {prop_name} not defined on socket {self.bl_label} ({self.socket_type})'
|
## -> Dependent props will generally also trigger on_prop_changed.
|
||||||
raise RuntimeError(msg)
|
## -> The recursion ends with the depschain.
|
||||||
|
## -> WARNING: The chain is not checked for ex. cycles.
|
||||||
|
if prop_name in self.blfields:
|
||||||
|
self.invalidate_blfield_deps(prop_name)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Link Event: Consent / On Change
|
# - Link Event: Consent / On Change
|
||||||
|
|
|
@ -219,9 +219,57 @@ class BLInstance:
|
||||||
for str_search_prop_name in self.blfields_str_search:
|
for str_search_prop_name in self.blfields_str_search:
|
||||||
setattr(self, str_search_prop_name, bl_cache.Signal.ResetStrSearch)
|
setattr(self, str_search_prop_name, bl_cache.Signal.ResetStrSearch)
|
||||||
|
|
||||||
|
def invalidate_blfield_deps(self, prop_name: str) -> None:
|
||||||
|
"""Invalidates all properties that depend on `prop_name`.
|
||||||
|
|
||||||
|
A property can recursively depend on other properties, including specificity as to whether the cache should be invalidated, the enum items be recomputed, or the string search items be recomputed.
|
||||||
|
|
||||||
|
This method actually implements this, by correctly invalidating all immediate dependents of `prop_name`.
|
||||||
|
As it is generally called during `self.on_bl_prop_changed()` / `self.on_prop_changed()`, invalidating immediate dependents is an implicitly recursive action.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The dictionaries governing exactly what invalidates what, and how, are encoded as `self.blfield_deps`, `self.blfield_dynamic_enum_deps`, and `self.blfield_str_search_deps`.
|
||||||
|
All of these are filled when creating the `BLInstance` subclass, using `self.declare_blfield_dep()`, generally via the `BLField` descriptor (which internally uses `BLProp`).
|
||||||
|
"""
|
||||||
|
# Invalidate Dependent Properties (incl. DynEnums and StrSearch)
|
||||||
|
## -> NOTE: Dependent props may also trigger `on_prop_changed`.
|
||||||
|
## -> Don't abuse dependencies :)
|
||||||
|
for deps, invalidate_signal in zip(
|
||||||
|
[
|
||||||
|
self.blfield_deps,
|
||||||
|
self.blfield_dynamic_enum_deps,
|
||||||
|
self.blfield_str_search_deps,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
bl_cache.Signal.InvalidateCache,
|
||||||
|
bl_cache.Signal.ResetEnumItems,
|
||||||
|
bl_cache.Signal.ResetStrSearch,
|
||||||
|
],
|
||||||
|
strict=True,
|
||||||
|
):
|
||||||
|
if prop_name in deps:
|
||||||
|
for dst_prop_name in deps[prop_name]:
|
||||||
|
log.debug(
|
||||||
|
'Property %s is invalidating %s',
|
||||||
|
prop_name,
|
||||||
|
dst_prop_name,
|
||||||
|
)
|
||||||
|
setattr(
|
||||||
|
self,
|
||||||
|
dst_prop_name,
|
||||||
|
invalidate_signal,
|
||||||
|
)
|
||||||
|
|
||||||
def on_bl_prop_changed(self, bl_prop_name: str, _: bpy.types.Context) -> None:
|
def on_bl_prop_changed(self, bl_prop_name: str, _: bpy.types.Context) -> None:
|
||||||
"""Called when a property has been updated via the Blender UI.
|
"""Called when a property has been updated via the Blender UI.
|
||||||
|
|
||||||
|
In general, **all** Blender UI properties in the entire program will call this method using `update`.
|
||||||
|
Whether anything further happens is a little more nuanced.
|
||||||
|
|
||||||
|
1. The cache of the `prop_name` associated with `bl_prop_name` is invalidated, but without invoking a cache update.
|
||||||
|
|
||||||
|
Primarily, `self.invalidate_blfield_deps()`
|
||||||
|
|
||||||
The only effect is to invalidate the non-persistent cache of the associated BLField.
|
The only effect is to invalidate the non-persistent cache of the associated BLField.
|
||||||
The BLField then decides whether to take any other action, ex. calling `self.on_prop_changed()`.
|
The BLField then decides whether to take any other action, ex. calling `self.on_prop_changed()`.
|
||||||
"""
|
"""
|
||||||
|
@ -230,61 +278,11 @@ class BLInstance:
|
||||||
# Strip the Internal Prefix
|
# Strip the Internal Prefix
|
||||||
## -> TODO: This is a bit of a hack. Use a contracts constant.
|
## -> TODO: This is a bit of a hack. Use a contracts constant.
|
||||||
prop_name = bl_prop_name.removeprefix('blfield__')
|
prop_name = bl_prop_name.removeprefix('blfield__')
|
||||||
# log.debug(
|
|
||||||
# 'Callback on Property %s (stripped: %s)',
|
|
||||||
# bl_prop_name,
|
|
||||||
# prop_name,
|
|
||||||
# )
|
|
||||||
# log.debug(
|
|
||||||
# 'Dependencies (PROP: %s) (ENUM: %s) (SEAR: %s)',
|
|
||||||
# self.blfield_deps,
|
|
||||||
# self.blfield_dynamic_enum_deps,
|
|
||||||
# self.blfield_str_search_deps,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# Invalidate Property Cache
|
# Invalidate Property Cache
|
||||||
## -> Only the non-persistent cache is regenerated.
|
|
||||||
## -> The BLField decides whether to trigger `on_prop_changed`.
|
## -> The BLField decides whether to trigger `on_prop_changed`.
|
||||||
if prop_name in self.blfields:
|
if prop_name in self.blfields:
|
||||||
# RULE: =1 DataChanged per Dependency Chain
|
setattr(self, prop_name, bl_cache.Signal.InvalidateCache)
|
||||||
## -> We MUST invalidate the cache, but might not want to update.
|
|
||||||
## -> Update should only be triggered when ==0 dependents.
|
|
||||||
setattr(self, prop_name, bl_cache.Signal.InvalidateCacheNoUpdate)
|
|
||||||
|
|
||||||
# Invalidate Dependent Properties (incl. DynEnums and StrSearch)
|
|
||||||
## -> NOTE: Dependent props may also trigger `on_prop_changed`.
|
|
||||||
## -> Meaning, don't use extraneous dependencies (as usual).
|
|
||||||
for deps, invalidate_signal in zip(
|
|
||||||
[
|
|
||||||
self.blfield_deps,
|
|
||||||
self.blfield_dynamic_enum_deps,
|
|
||||||
self.blfield_str_search_deps,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
bl_cache.Signal.InvalidateCache,
|
|
||||||
bl_cache.Signal.ResetEnumItems,
|
|
||||||
bl_cache.Signal.ResetStrSearch,
|
|
||||||
],
|
|
||||||
strict=True,
|
|
||||||
):
|
|
||||||
if prop_name in deps:
|
|
||||||
for dst_prop_name in deps[prop_name]:
|
|
||||||
# log.debug(
|
|
||||||
# 'Property %s is invalidating %s',
|
|
||||||
# prop_name,
|
|
||||||
# dst_prop_name,
|
|
||||||
# )
|
|
||||||
setattr(
|
|
||||||
self,
|
|
||||||
dst_prop_name,
|
|
||||||
invalidate_signal,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Do Update AFTER Dependencies
|
|
||||||
## -> Yes, update will run once per dependency.
|
|
||||||
## -> Don't abuse dependencies :)
|
|
||||||
## -> If no-update is important, use_prop_update is still respected.
|
|
||||||
setattr(self, prop_name, bl_cache.Signal.DoUpdate)
|
|
||||||
|
|
||||||
def on_prop_changed(self, prop_name: str) -> None:
|
def on_prop_changed(self, prop_name: str) -> None:
|
||||||
"""Triggers changes/an event chain based on a changed property.
|
"""Triggers changes/an event chain based on a changed property.
|
||||||
|
|
Loading…
Reference in New Issue