summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Connors <benconnors@outlook.com>2019-11-30 16:45:18 -0500
committerBen Connors <benconnors@outlook.com>2019-11-30 16:45:18 -0500
commit61df2609bec5a4493c0839dbc0983dc2356fb1f2 (patch)
tree91e3fd96c279b13806fd85f8f4c06dca6f51c3f0
parent4e2e73e1bce3cf51107d7fe1314a92fd519f03f2 (diff)
Implement join function
-rw-r--r--blc2/constants.py1
-rw-r--r--blc2/functions/join.py146
-rw-r--r--blc2/workspace.py11
3 files changed, 157 insertions, 1 deletions
diff --git a/blc2/constants.py b/blc2/constants.py
index bb207f9..ca43367 100644
--- a/blc2/constants.py
+++ b/blc2/constants.py
@@ -66,6 +66,7 @@ SCENE = "Scene"
AUDIO = "Audio"
CHASER = "Chaser"
CHASERSTEP = "ChaserStep"
+JOIN = "Join"
FUNCTION = "Function"
INTERNAL = "Internal"
diff --git a/blc2/functions/join.py b/blc2/functions/join.py
new file mode 100644
index 0000000..9ab2f73
--- /dev/null
+++ b/blc2/functions/join.py
@@ -0,0 +1,146 @@
+"""Module for join functions."""
+
+import xml.etree.ElementTree as et
+
+from ..constants import JOIN, INFTY, BXW
+from .function import Function
+from ..exceptions import LoadError
+
+class Join(Function):
+ """Class for joins.
+
+ Joins merge multiple functions into one (e.g. audio and lighting).
+ """
+ type = JOIN
+
+ def __init__(self, w, id_ = None, name = None):
+ super().__init__(w=w, id_=id_, name=name)
+
+ self._steps = []
+ self._duration = 0
+ self._actual_duration = 0
+
+ self._scope = frozenset()
+ self._audio_scope = frozenset()
+
+ def _recalculate(self, update=True):
+ self._duration = max(0, *(s.duration for s in self._steps))
+ self._actual_duration = max(0, *(s.actual_duration for s in self._steps))
+ self._scope = frozenset().union(*(s.scope for s in self._steps))
+ self._audio_scope = frozenset().union(*(s.audio_scope for s in self._steps))
+
+ if update:
+ self.w.function_changed(self)
+
+ @property
+ def actual_duration(self):
+ return self._actual_duration
+
+ @property
+ def duration(self):
+ return self._duration
+
+ @property
+ def scope(self):
+ return self._scope
+
+ @property
+ def audio_scope(self):
+ return self._audio_scope
+
+ @property
+ def fade_in(self):
+ return 0
+
+ @property
+ def fade_out(self):
+ return 0
+
+ def _function_changed(self, _):
+ self._recalculate()
+
+ def get_data(self, params=None):
+ if params is not None:
+ if len(params) != len(self._steps):
+ raise ValueError("Must have same number of parameters as steps!")
+ return [s.get_data(*p) for s, p in zip(self._steps, params)]
+ return [s.get_data() for s in self._steps]
+
+ def copy_data(self, d):
+ return [s.copy_data(d) for s, d in zip(self.steps, d)]
+
+ @property
+ def steps(self):
+ return tuple(self._steps)
+
+ def add_step(self, s):
+ if s.id in (i.id for i in self._steps):
+ raise ValueError("Already added")
+ self._steps.insert(-1, s)
+ self._recalculate()
+
+ self.w.register_function_change_callback(s, self._recalculate, self)
+ self.w.register_function_delete_ballback(s, self._recalculate, self)
+
+ def delete_step(self, s, index=False):
+ if index:
+ s = self._steps[s]
+ elif isinstance(s, int):
+ s = self.w.functions[s]
+
+ self._steps.remove(s)
+
+ self.w.delete_callbacks(self, s)
+ self._recalculate()
+
+ def render(self, t, data=None):
+ if data is None:
+ data = self.get_data()
+
+ lc = {c: 0 for c in self._scope}
+ ac = []
+
+ for i, s in enumerate(self.steps):
+ lcs, acs, data[i] = s.render(t, data=data[i])
+ for c, v in lcs:
+ if v > lc[c]:
+ lc[c] = v
+ ac.extend(((hash((self._id, aid)), *others) for aid, *others in acs))
+
+ return tuple(lc.items()), tuple(ac), data
+
+ def serialize(self):
+ e = et.Element(BXW+"function")
+ e.set("type", self.type)
+ e.set("id", str(self.id))
+ e.set("name", self.name)
+ for s in self.steps:
+ se = et.SubElement(e, BXW+"member")
+ se.set("id", s.id)
+
+ return e
+
+ @classmethod
+ def deserialize(cls, w, e):
+ if e.tag != BXW+"function":
+ raise LoadError("Invalid function tag")
+ elif e.get("type") != JOIN:
+ raise LoadError("Load delegated to wrong class (this is a bug)")
+
+ id_ = cls.int_or_none(e.get("id"))
+ if id_ is None:
+ raise LoadError("Function tag has invalid/missing ID")
+
+ name = e.get("name")
+
+ c = cls(w=w, id_=id_, name=name)
+
+ for se in e:
+ if se.tag != BXW+"member":
+ raise LoadError("Invalid member tag")
+
+ fid = cls.int_or_none(se.get("id"))
+ if fid is None:
+ raise LoadError("Missing/invalid member id")
+
+ c.add_step(w.functions[fid])
diff --git a/blc2/workspace.py b/blc2/workspace.py
index 8abf8a1..1983ac7 100644
--- a/blc2/workspace.py
+++ b/blc2/workspace.py
@@ -8,7 +8,7 @@ import json
import subprocess as subp
import xml.etree.ElementTree as et
-from .constants import AUDIO, SCENE, BXW, CHASER, CHASERSTEP
+from .constants import AUDIO, SCENE, BXW, CHASER, CHASERSTEP, JOIN
from .functions.function import Function
from .exceptions import LoadError
from .interfaces import XMLSerializable
@@ -292,6 +292,15 @@ class Workspace(XMLSerializable):
done.add(f.id)
continue
all_f.append(f)
+ elif f.type in (JOIN,):
+ for step in f.steps:
+ if step.id not in done:
+ break
+ else:
+ f_order.append(f)
+ done.add(f.id)
+ continue
+ all_f.append(f)
else:
raise ValueError("Unknown function type "+f.type)