ScriptEditCommand.hpp
1 #pragma once
2 #include <Process/Dataflow/Port.hpp>
3 #include <Process/Process.hpp>
4 #include <Process/Script/ScriptEditor.hpp>
5 #include <Process/Script/ScriptProcess.hpp>
6 
7 #include <Scenario/Document/ScenarioDocument/ScenarioDocumentModel.hpp>
8 
9 #include <Dataflow/Commands/CableHelpers.hpp>
10 
11 #include <ossia/detail/algorithms.hpp>
12 namespace Scenario
13 {
14 struct SavedPort
15 {
16  QString name;
17  Process::PortType type;
18  QByteArray data;
19 };
20 
21 template <typename Process_T, typename Property_T>
22 class EditScript : public score::Command
23 {
24 public:
25  using param_type = typename Property_T::param_type;
26  using score::Command::Command;
27  EditScript(
28  const Process_T& model, param_type newScript, const score::DocumentContext& ctx)
29  : m_path{model}
30  , m_newScript{std::move(newScript)}
31  , m_oldScript{(model.*Property_T::get)()}
32  {
33  m_oldCables = Dataflow::saveCables({const_cast<Process_T*>(&model)}, ctx);
34 
35  for(auto& port : model.inlets())
36  m_oldInlets.emplace_back(SavedPort{port->name(), port->type(), port->saveData()});
37  for(auto& port : model.outlets())
38  m_oldOutlets.emplace_back(SavedPort{port->name(), port->type(), port->saveData()});
39  }
40 
41 private:
42  void undo(const score::DocumentContext& ctx) const override
43  {
44  auto& cmt = m_path.find(ctx);
45  // Remove all the cables that could have been added during
46  // the creation
47  Dataflow::removeCables(m_oldCables, ctx);
48 
49  // Set the old script
50  Process::ScriptChangeResult res = (cmt.*Property_T::set)(m_oldScript);
51  cmt.programChanged();
52 
53  // We expect the inputs / outputs to revert back to the
54  // exact same state
55  SCORE_ASSERT(m_oldInlets.size() == cmt.inlets().size());
56  SCORE_ASSERT(m_oldOutlets.size() == cmt.outlets().size());
57 
58  // So we can reload their data identically
59  for(std::size_t i = 0; i < m_oldInlets.size(); i++)
60  {
61  cmt.inlets()[i]->loadData(m_oldInlets[i].data);
62  }
63  for(std::size_t i = 0; i < m_oldOutlets.size(); i++)
64  {
65  cmt.outlets()[i]->loadData(m_oldOutlets[i].data);
66  }
67 
68  // Recreate the old cables
69  Dataflow::restoreCables(m_oldCables, ctx);
70  cmt.inletsChanged();
71  cmt.outletsChanged();
72  if constexpr(requires { cmt.isGpu(); })
73  if(cmt.isGpu())
74  cmt.programChanged();
75  }
76 
77  static void restoreCables(
78  Process::Inlet& new_p, Scenario::ScenarioDocumentModel& doc,
79  const score::DocumentContext& ctx, const Dataflow::SerializedCables& cables)
80  {
81  for(auto& cable : new_p.cables())
82  {
83  SCORE_ASSERT(!cable.unsafePath().vec().empty());
84  auto cable_id = cable.unsafePath().vec().back().id();
85  auto it = ossia::find_if(
86  cables, [cable_id](auto& c) { return c.first.val() == cable_id; });
87 
88  SCORE_ASSERT(it != cables.end());
89  SCORE_ASSERT(doc.cables.find(it->first) == doc.cables.end());
90  {
91  auto c = new Process::Cable{it->first, it->second, &doc};
92  doc.cables.add(c);
93  c->source().find(ctx).addCable(*c);
94  }
95  }
96  }
97  static void restoreCables(
99  const score::DocumentContext& ctx, const Dataflow::SerializedCables& cables)
100  {
101  for(auto& cable : new_p.cables())
102  {
103  SCORE_ASSERT(!cable.unsafePath().vec().empty());
104  auto cable_id = cable.unsafePath().vec().back().id();
105  auto it = ossia::find_if(
106  cables, [cable_id](auto& c) { return c.first.val() == cable_id; });
107 
108  SCORE_ASSERT(it != cables.end());
109  SCORE_ASSERT(doc.cables.find(it->first) == doc.cables.end());
110  {
111  auto c = new Process::Cable{it->first, it->second, &doc};
112  doc.cables.add(c);
113  c->sink().find(ctx).addCable(*c);
114  }
115  }
116  }
117 
118  void redo(const score::DocumentContext& ctx) const override
119  {
120  Dataflow::removeCables(m_oldCables, ctx);
121 
122  auto& cmt = m_path.find(ctx);
123  Process::ScriptChangeResult res = (cmt.*Property_T::set)(m_newScript);
124  cmt.programChanged();
125 
126  // Try an optimistic matching. Type and name must match.
127  auto& doc = score::IDocument::get<Scenario::ScenarioDocumentModel>(ctx.document);
128 
129  std::size_t min_inlets = std::min(m_oldInlets.size(), cmt.inlets().size());
130  std::size_t min_outlets = std::min(m_oldOutlets.size(), cmt.outlets().size());
131  for(std::size_t i = 0; i < min_inlets; i++)
132  {
133  auto new_p = cmt.inlets()[i];
134  auto& old_p = m_oldInlets[i];
135 
136  if(new_p->type() == old_p.type && new_p->name() == old_p.name)
137  {
138  new_p->loadData(old_p.data);
139  restoreCables(*new_p, doc, ctx, m_oldCables);
140  }
141  }
142 
143  for(std::size_t i = 0; i < min_outlets; i++)
144  {
145  auto new_p = cmt.outlets()[i];
146  auto& old_p = m_oldOutlets[i];
147 
148  if(new_p->type() == old_p.type && new_p->name() == old_p.name)
149  {
150  new_p->loadData(old_p.data);
151  restoreCables(*new_p, doc, ctx, m_oldCables);
152  }
153  }
154 
155  cmt.inletsChanged();
156  cmt.outletsChanged();
157  if constexpr(requires { cmt.isGpu(); })
158  if(cmt.isGpu())
159  cmt.programChanged();
160  // FIXME if we have it only here, then changing cables fails for the exec nodes
161  // as in the cable loading, in SetupContext::connectCable(Process::Cable& cable)
162  // auto it = outlets.find(port_src); fails because the new outlet hasn't yet been created by the component
163  // but if we have it only above, the JS GPU node fails
164  }
165 
166  void serializeImpl(DataStreamInput& s) const override
167  {
168  s << m_path << m_newScript << m_oldScript << m_oldInlets << m_oldOutlets
169  << m_oldCables;
170  }
171 
172  void deserializeImpl(DataStreamOutput& s) override
173  {
174  s >> m_path >> m_newScript >> m_oldScript >> m_oldInlets >> m_oldOutlets
175  >> m_oldCables;
176  }
177 
178  Path<Process_T> m_path;
179  param_type m_newScript;
180  param_type m_oldScript;
181 
182  std::vector<SavedPort> m_oldInlets, m_oldOutlets;
183 
184  Dataflow::SerializedCables m_oldCables;
185 };
186 }
187 
188 template <>
189 struct is_custom_serialized<Scenario::SavedPort> : std::true_type
190 {
191 };
192 
193 template <>
194 struct TSerializer<DataStream, Scenario::SavedPort>
195 {
196  static void readFrom(DataStream::Serializer& s, const Scenario::SavedPort& tv)
197  {
198  s.stream() << tv.name << tv.type << tv.data;
199  }
200 
201  static void writeTo(DataStream::Deserializer& s, Scenario::SavedPort& tv)
202  {
203  s.stream() >> tv.name >> tv.type >> tv.data;
204  }
205 };
Definition: VisitorInterface.hpp:53
Definition: DataStreamVisitor.hpp:27
Definition: DataStreamVisitor.hpp:202
Definition: Cable.hpp:54
Definition: score-lib-process/Process/Dataflow/Port.hpp:264
Definition: ScriptEditCommand.hpp:23
Definition: ScenarioDocumentModel.hpp:29
The Command class.
Definition: Command.hpp:34
Base classes and tools to implement processes and layers.
Definition: JSONVisitor.hpp:1324
Main plug-in of score.
Definition: score-plugin-dataflow/Dataflow/PortItem.hpp:14
Base toolkit upon which the software is built.
Definition: Application.cpp:90
Definition: DataStreamHelpers.hpp:99
Definition: DataStreamHelpers.hpp:103
Definition: ScriptProcess.hpp:14
Definition: ScriptEditCommand.hpp:15
Definition: VisitorInterface.hpp:13
Definition: DocumentContext.hpp:18