From f84a4bd4045f0265cffae2ad3e83967c062ff5a2 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 28 Oct 2019 19:00:05 -0400 Subject: [PATCH] Fix interesting empty node socket spawning bugs. This fixes two bugs with empty node socket spawning: - The first time an empty is spawned, the link is moved to the last socket. - Unlinking a socket in the middle of the group would cause that socket to be the one that's empty. This isn't bad per-se, but with Responders being ID-based, it could result in some confusion. --- korman/nodes/node_core.py | 161 +++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 79 deletions(-) diff --git a/korman/nodes/node_core.py b/korman/nodes/node_core.py index ff3e4f3..04f935d 100644 --- a/korman/nodes/node_core.py +++ b/korman/nodes/node_core.py @@ -300,92 +300,95 @@ class PlasmaNodeBase: """Ensures that sockets are linked appropriately and there are enough inputs""" input_defs, output_defs = self._socket_defs for defs, sockets in ((input_defs, self.inputs), (output_defs, self.outputs)): - done = set() - - # Need to enumerate by hand because blendsucks has major (crashing) issues if we modify - # this swhizzle while stuff is going down. - i = 0 - while i < len(sockets): - socket = sockets[i] - node = socket.node - - options = defs.get(socket.alias, None) - if options is None or socket.bl_idname != options["type"]: - sockets.remove(socket) - continue + self._update_extant_sockets(defs, sockets) + self._update_init_sockets(defs, sockets) + + def _update_init_sockets(self, defs, sockets): + # Create any missing sockets and spawn any required empties. + for alias, options in defs.items(): + working_sockets = [(i, socket) for i, socket in enumerate(sockets) if socket.alias == alias] + if not working_sockets: + self._spawn_socket(alias, options, sockets) + elif options.get("spawn_empty", False): + last_socket_id = next(reversed(working_sockets))[0] + for working_id, working_socket in working_sockets: + if working_id == last_socket_id and working_socket.is_linked: + new_socket_id = len(sockets) + new_socket = self._spawn_socket(alias, options, sockets) + desired_id = last_socket_id + 1 + if new_socket_id != desired_id: + sockets.move(new_socket_id, desired_id) + elif working_id < last_socket_id and not working_socket.is_linked: + # Indices do not update until after the update() function finishes, so + # no need to decrement last_socket_id + sockets.remove(working_socket) + + def _update_extant_sockets(self, defs, sockets): + # Manually enumerate the sockets that are present for their presence and for the + # validity of their links. Can't use a for because we will overrun and crash Blender. + i = 0 + while i < len(sockets): + socket = sockets[i] + node = socket.node + + options = defs.get(socket.alias, None) + if options is None or socket.bl_idname != options["type"]: + sockets.remove(socket) + continue - # Make sure the socket info is up to date - socket.name = options["text"] - link_limit = options.get("link_limit", None) - if link_limit is not None: - socket.link_limit = link_limit - socket.hide = options.get("hidden", False) - socket.hide_value = options.get("hidden", False) - - # Make sure the link is good - allowed_sockets = options.get("valid_link_sockets", None) - allowed_nodes = options.get("valid_link_nodes", None) - - # The socket may decide it doesn't want anyone linked to it. - can_link_attr = options.get("can_link", None) - if can_link_attr is not None: - can_link = getattr(node, can_link_attr) - socket.enabled = can_link - if not can_link: - for link in socket.links: + # Make sure the socket info is up to date + socket.name = options["text"] + link_limit = options.get("link_limit", None) + if link_limit is not None: + socket.link_limit = link_limit + socket.hide = options.get("hidden", False) + socket.hide_value = options.get("hidden", False) + + # Make sure the link is good + allowed_sockets = options.get("valid_link_sockets", None) + allowed_nodes = options.get("valid_link_nodes", None) + + # The socket may decide it doesn't want anyone linked to it. + can_link_attr = options.get("can_link", None) + if can_link_attr is not None: + can_link = getattr(node, can_link_attr) + socket.enabled = can_link + if not can_link: + for link in socket.links: + try: + self._tattle(socket, link, "(socket refused link)") + self.id_data.links.remove(link) + except RuntimeError: + # was already removed by someone else + pass + + # Helpful default... If neither are set, require the link to be to the same socket type + if allowed_nodes is None and allowed_sockets is None: + allowed_sockets = frozenset((options["type"],)) + if allowed_sockets or allowed_nodes: + for link in socket.links: + if allowed_nodes: + to_from_node = link.to_node if socket.is_output else link.from_node + if to_from_node.bl_idname not in allowed_nodes: try: - self._tattle(socket, link, "(socket refused link)") + self._tattle(socket, link, "(bad node)") self.id_data.links.remove(link) except RuntimeError: # was already removed by someone else pass + continue + if allowed_sockets: + to_from_socket = link.to_socket if socket.is_output else link.from_socket + if to_from_socket is None or to_from_socket.bl_idname not in allowed_sockets: + try: + self._tattle(socket, link, "(bad socket)") + self.id_data.links.remove(link) + except RuntimeError: + # was already removed by someone else + pass + continue - # Helpful default... If neither are set, require the link to be to the same socket type - if allowed_nodes is None and allowed_sockets is None: - allowed_sockets = frozenset((options["type"],)) - if allowed_sockets or allowed_nodes: - for link in socket.links: - if allowed_nodes: - to_from_node = link.to_node if socket.is_output else link.from_node - if to_from_node.bl_idname not in allowed_nodes: - try: - self._tattle(socket, link, "(bad node)") - self.id_data.links.remove(link) - except RuntimeError: - # was already removed by someone else - pass - continue - if allowed_sockets: - to_from_socket = link.to_socket if socket.is_output else link.from_socket - if to_from_socket is None or to_from_socket.bl_idname not in allowed_sockets: - try: - self._tattle(socket, link, "(bad socket)") - self.id_data.links.remove(link) - except RuntimeError: - # was already removed by someone else - pass - continue - - # If this is a spawn empty socket, make sure we have exactly one empty socket - if options.get("spawn_empty", False) and not socket.alias in done: - empty_sockets = [j for j in sockets if j.bl_idname == socket.bl_idname and not j.is_used] - if not empty_sockets: - idx = len(sockets) - dbg = sockets.new(socket.bl_idname, socket.name, socket.alias) - # don't even ask... - new_idx = i + 2 - if idx != new_idx: - sockets.move(idx, new_idx) - else: - while len(empty_sockets) > 1: - sockets.remove(empty_sockets.pop()) - done.add(socket.alias) - - i += 1 - - # Create any new sockets - for alias in (j for j in defs if j not in done): - self._spawn_socket(alias, defs[alias], sockets) + i += 1 def _whine(self, msg, *args): if args: