Cleanup: remove non-existing function declarations
[blender.git] / source / blender / compositor / intern / COM_NodeOperationBuilder.cpp
1 /*
2  * Copyright 2013, Blender Foundation.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor:
19  *      Lukas Toenne
20  */
21
22 extern "C" {
23 #include "BLI_utildefines.h"
24 }
25
26 #include "COM_NodeConverter.h"
27 #include "COM_Converter.h"
28 #include "COM_Debug.h"
29 #include "COM_ExecutionSystem.h"
30 #include "COM_Node.h"
31 #include "COM_SocketProxyNode.h"
32
33 #include "COM_NodeOperation.h"
34 #include "COM_PreviewOperation.h"
35 #include "COM_SetValueOperation.h"
36 #include "COM_SetVectorOperation.h"
37 #include "COM_SetColorOperation.h"
38 #include "COM_SocketProxyOperation.h"
39 #include "COM_ReadBufferOperation.h"
40 #include "COM_WriteBufferOperation.h"
41 #include "COM_ViewerOperation.h"
42
43 #include "COM_NodeOperationBuilder.h" /* own include */
44
45 NodeOperationBuilder::NodeOperationBuilder(const CompositorContext *context, bNodeTree *b_nodetree) :
46     m_context(context),
47     m_current_node(NULL),
48     m_active_viewer(NULL)
49 {
50         m_graph.from_bNodeTree(*context, b_nodetree);
51 }
52
53 NodeOperationBuilder::~NodeOperationBuilder()
54 {
55 }
56
57 void NodeOperationBuilder::convertToOperations(ExecutionSystem *system)
58 {
59         /* interface handle for nodes */
60         NodeConverter converter(this);
61
62         for (int index = 0; index < m_graph.nodes().size(); index++) {
63                 Node *node = (Node *)m_graph.nodes()[index];
64
65                 m_current_node = node;
66
67                 DebugInfo::node_to_operations(node);
68                 node->convertToOperations(converter, *m_context);
69         }
70
71         m_current_node = NULL;
72
73         /* The input map constructed by nodes maps operation inputs to node inputs.
74          * Inverting yields a map of node inputs to all connected operation inputs,
75          * so multiple operations can use the same node input.
76          */
77         OpInputInverseMap inverse_input_map;
78         for (InputSocketMap::const_iterator it = m_input_map.begin(); it != m_input_map.end(); ++it)
79                 inverse_input_map[it->second].push_back(it->first);
80
81         for (NodeGraph::Links::const_iterator it = m_graph.links().begin(); it != m_graph.links().end(); ++it) {
82                 const NodeGraph::Link &link = *it;
83                 NodeOutput *from = link.getFromSocket();
84                 NodeInput *to = link.getToSocket();
85
86                 NodeOperationOutput *op_from = find_operation_output(m_output_map, from);
87                 const OpInputs &op_to_list = find_operation_inputs(inverse_input_map, to);
88                 if (!op_from || op_to_list.empty()) {
89                         /* XXX allow this? error/debug message? */
90                         //BLI_assert(false);
91                         /* XXX note: this can happen with certain nodes (e.g. OutputFile)
92                          * which only generate operations in certain circumstances (rendering)
93                          * just let this pass silently for now ...
94                          */
95                         continue;
96                 }
97
98                 for (OpInputs::const_iterator it = op_to_list.begin(); it != op_to_list.end(); ++it) {
99                         NodeOperationInput *op_to = *it;
100                         addLink(op_from, op_to);
101                 }
102         }
103
104         add_operation_input_constants();
105
106         resolve_proxies();
107
108         add_datatype_conversions();
109
110         determineResolutions();
111
112         /* surround complex ops with read/write buffer */
113         add_complex_operation_buffers();
114
115         /* links not available from here on */
116         /* XXX make m_links a local variable to avoid confusion! */
117         m_links.clear();
118
119         prune_operations();
120
121         /* ensure topological (link-based) order of nodes */
122         /*sort_operations();*/ /* not needed yet */
123
124         /* create execution groups */
125         group_operations();
126
127         /* transfer resulting operations to the system */
128         system->set_operations(m_operations, m_groups);
129 }
130
131 void NodeOperationBuilder::addOperation(NodeOperation *operation)
132 {
133         m_operations.push_back(operation);
134 }
135
136 void NodeOperationBuilder::mapInputSocket(NodeInput *node_socket, NodeOperationInput *operation_socket)
137 {
138         BLI_assert(m_current_node);
139         BLI_assert(node_socket->getNode() == m_current_node);
140
141         /* note: this maps operation sockets to node sockets.
142          * for resolving links the map will be inverted first in convertToOperations,
143          * to get a list of links for each node input socket.
144          */
145         m_input_map[operation_socket] = node_socket;
146 }
147
148 void NodeOperationBuilder::mapOutputSocket(NodeOutput *node_socket, NodeOperationOutput *operation_socket)
149 {
150         BLI_assert(m_current_node);
151         BLI_assert(node_socket->getNode() == m_current_node);
152
153         m_output_map[node_socket] = operation_socket;
154 }
155
156 void NodeOperationBuilder::addLink(NodeOperationOutput *from, NodeOperationInput *to)
157 {
158         if (to->isConnected())
159                 return;
160
161         m_links.push_back(Link(from, to));
162
163         /* register with the input */
164         to->setLink(from);
165 }
166
167 void NodeOperationBuilder::removeInputLink(NodeOperationInput *to)
168 {
169         for (Links::iterator it = m_links.begin(); it != m_links.end(); ++it) {
170                 Link &link = *it;
171                 if (link.to() == to) {
172                         /* unregister with the input */
173                         to->setLink(NULL);
174
175                         m_links.erase(it);
176                         return;
177                 }
178         }
179 }
180
181 NodeInput *NodeOperationBuilder::find_node_input(const InputSocketMap &map, NodeOperationInput *op_input)
182 {
183         InputSocketMap::const_iterator it = map.find(op_input);
184         return (it != map.end() ? it->second : NULL);
185 }
186
187 const NodeOperationBuilder::OpInputs &NodeOperationBuilder::find_operation_inputs(const OpInputInverseMap &map, NodeInput *node_input)
188 {
189         static const OpInputs empty_list;
190         OpInputInverseMap::const_iterator it = map.find(node_input);
191         return (it != map.end() ? it->second : empty_list);
192 }
193
194 NodeOperationOutput *NodeOperationBuilder::find_operation_output(const OutputSocketMap &map, NodeOutput *node_output)
195 {
196         OutputSocketMap::const_iterator it = map.find(node_output);
197         return (it != map.end() ? it->second : NULL);
198 }
199
200 PreviewOperation *NodeOperationBuilder::make_preview_operation() const
201 {
202         BLI_assert(m_current_node);
203
204         if (!(m_current_node->getbNode()->flag & NODE_PREVIEW))
205                 return NULL;
206         /* previews only in the active group */
207         if (!m_current_node->isInActiveGroup())
208                 return NULL;
209         /* do not calculate previews of hidden nodes */
210         if (m_current_node->getbNode()->flag & NODE_HIDDEN)
211                 return NULL;
212
213         bNodeInstanceHash *previews = m_context->getPreviewHash();
214         if (previews) {
215                 PreviewOperation *operation = new PreviewOperation(m_context->getViewSettings(), m_context->getDisplaySettings());
216                 operation->setbNodeTree(m_context->getbNodeTree());
217                 operation->verifyPreview(previews, m_current_node->getInstanceKey());
218                 return operation;
219         }
220
221         return NULL;
222 }
223
224 void NodeOperationBuilder::addPreview(NodeOperationOutput *output)
225 {
226         PreviewOperation *operation = make_preview_operation();
227         if (operation) {
228                 addOperation(operation);
229
230                 addLink(output, operation->getInputSocket(0));
231         }
232 }
233
234 void NodeOperationBuilder::addNodeInputPreview(NodeInput *input)
235 {
236         PreviewOperation *operation = make_preview_operation();
237         if (operation) {
238                 addOperation(operation);
239
240                 mapInputSocket(input, operation->getInputSocket(0));
241         }
242 }
243
244 void NodeOperationBuilder::registerViewer(ViewerOperation *viewer)
245 {
246         if (m_active_viewer) {
247                 if (m_current_node->isInActiveGroup()) {
248                         /* deactivate previous viewer */
249                         m_active_viewer->setActive(false);
250
251                         m_active_viewer = viewer;
252                         viewer->setActive(true);
253                 }
254         }
255         else {
256                 if (m_current_node->getbNodeTree() == m_context->getbNodeTree()) {
257                         m_active_viewer = viewer;
258                         viewer->setActive(true);
259                 }
260         }
261 }
262
263 /****************************
264  **** Optimization Steps ****
265  ****************************/
266
267 void NodeOperationBuilder::add_datatype_conversions()
268 {
269         Links convert_links;
270         for (Links::const_iterator it = m_links.begin(); it != m_links.end(); ++it) {
271                 const Link &link = *it;
272
273                 /* proxy operations can skip data type conversion */
274                 NodeOperation *from_op = &link.from()->getOperation();
275                 NodeOperation *to_op = &link.to()->getOperation();
276                 if (!(from_op->useDatatypeConversion() || to_op->useDatatypeConversion()))
277                         continue;
278
279                 if (link.from()->getDataType() != link.to()->getDataType())
280                         convert_links.push_back(link);
281         }
282         for (Links::const_iterator it = convert_links.begin(); it != convert_links.end(); ++it) {
283                 const Link &link = *it;
284                 NodeOperation *converter = Converter::convertDataType(link.from(), link.to());
285                 if (converter) {
286                         addOperation(converter);
287
288                         removeInputLink(link.to());
289                         addLink(link.from(), converter->getInputSocket(0));
290                         addLink(converter->getOutputSocket(0), link.to());
291                 }
292         }
293 }
294
295 void NodeOperationBuilder::add_operation_input_constants()
296 {
297         /* Note: unconnected inputs cached first to avoid modifying
298          *       m_operations while iterating over it
299          */
300         typedef std::vector<NodeOperationInput*> Inputs;
301         Inputs pending_inputs;
302         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it) {
303                 NodeOperation *op = *it;
304                 for (int k = 0; k < op->getNumberOfInputSockets(); ++k) {
305                         NodeOperationInput *input = op->getInputSocket(k);
306                         if (!input->isConnected())
307                                 pending_inputs.push_back(input);
308                 }
309         }
310         for (Inputs::const_iterator it = pending_inputs.begin(); it != pending_inputs.end(); ++it) {
311                 NodeOperationInput *input = *it;
312                 add_input_constant_value(input, find_node_input(m_input_map, input));
313         }
314 }
315
316 void NodeOperationBuilder::add_input_constant_value(NodeOperationInput *input, NodeInput *node_input)
317 {
318         switch (input->getDataType()) {
319                 case COM_DT_VALUE: {
320                         float value;
321                         if (node_input && node_input->getbNodeSocket())
322                                 value = node_input->getEditorValueFloat();
323                         else
324                                 value = 0.0f;
325
326                         SetValueOperation *op = new SetValueOperation();
327                         op->setValue(value);
328                         addOperation(op);
329                         addLink(op->getOutputSocket(), input);
330                         break;
331                 }
332                 case COM_DT_COLOR: {
333                         float value[4];
334                         if (node_input && node_input->getbNodeSocket())
335                                 node_input->getEditorValueColor(value);
336                         else
337                                 zero_v4(value);
338
339                         SetColorOperation *op = new SetColorOperation();
340                         op->setChannels(value);
341                         addOperation(op);
342                         addLink(op->getOutputSocket(), input);
343                         break;
344                 }
345                 case COM_DT_VECTOR: {
346                         float value[3];
347                         if (node_input && node_input->getbNodeSocket())
348                                 node_input->getEditorValueVector(value);
349                         else
350                                 zero_v3(value);
351
352                         SetVectorOperation *op = new SetVectorOperation();
353                         op->setVector(value);
354                         addOperation(op);
355                         addLink(op->getOutputSocket(), input);
356                         break;
357                 }
358         }
359 }
360
361 void NodeOperationBuilder::resolve_proxies()
362 {
363         Links proxy_links;
364         for (Links::const_iterator it = m_links.begin(); it != m_links.end(); ++it) {
365                 const Link &link = *it;
366                 /* don't replace links from proxy to proxy, since we may need them for replacing others! */
367                 if (link.from()->getOperation().isProxyOperation() &&
368                     !link.to()->getOperation().isProxyOperation())
369                 {
370                         proxy_links.push_back(link);
371                 }
372         }
373
374         for (Links::const_iterator it = proxy_links.begin(); it != proxy_links.end(); ++it) {
375                 const Link &link = *it;
376
377                 NodeOperationInput *to = link.to();
378                 NodeOperationOutput *from = link.from();
379                 do {
380                         /* walk upstream bypassing the proxy operation */
381                         from = from->getOperation().getInputSocket(0)->getLink();
382                 } while (from && from->getOperation().isProxyOperation());
383
384                 removeInputLink(to);
385                 /* we may not have a final proxy input link,
386                  * in that case it just gets dropped
387                  */
388                 if (from)
389                         addLink(from, to);
390         }
391 }
392
393 void NodeOperationBuilder::determineResolutions()
394 {
395         /* determine all resolutions of the operations (Width/Height) */
396         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it) {
397                 NodeOperation *op = *it;
398
399                 if (op->isOutputOperation(m_context->isRendering()) && !op->isPreviewOperation()) {
400                         unsigned int resolution[2] = {0, 0};
401                         unsigned int preferredResolution[2] = {0, 0};
402                         op->determineResolution(resolution, preferredResolution);
403                         op->setResolution(resolution);
404                 }
405         }
406
407         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it) {
408                 NodeOperation *op = *it;
409
410                 if (op->isOutputOperation(m_context->isRendering()) && op->isPreviewOperation()) {
411                         unsigned int resolution[2] = {0, 0};
412                         unsigned int preferredResolution[2] = {0, 0};
413                         op->determineResolution(resolution, preferredResolution);
414                         op->setResolution(resolution);
415                 }
416         }
417
418         /* add convert resolution operations when needed */
419         {
420                 Links convert_links;
421                 for (Links::const_iterator it = m_links.begin(); it != m_links.end(); ++it) {
422                         const Link &link = *it;
423
424                         if (link.to()->getResizeMode() != COM_SC_NO_RESIZE) {
425                                 NodeOperation &from_op = link.from()->getOperation();
426                                 NodeOperation &to_op = link.to()->getOperation();
427                                 if (from_op.getWidth() != to_op.getWidth() || from_op.getHeight() != to_op.getHeight())
428                                         convert_links.push_back(link);
429                         }
430                 }
431                 for (Links::const_iterator it = convert_links.begin(); it != convert_links.end(); ++it) {
432                         const Link &link = *it;
433                         Converter::convertResolution(*this, link.from(), link.to());
434                 }
435         }
436 }
437
438 NodeOperationBuilder::OpInputs NodeOperationBuilder::cache_output_links(NodeOperationOutput *output) const
439 {
440         OpInputs inputs;
441         for (Links::const_iterator it = m_links.begin(); it != m_links.end(); ++it) {
442                 const Link &link = *it;
443                 if (link.from() == output)
444                         inputs.push_back(link.to());
445         }
446         return inputs;
447 }
448
449 WriteBufferOperation *NodeOperationBuilder::find_attached_write_buffer_operation(NodeOperationOutput *output) const
450 {
451         for (Links::const_iterator it = m_links.begin(); it != m_links.end(); ++it) {
452                 const Link &link = *it;
453                 if (link.from() == output) {
454                         NodeOperation &op = link.to()->getOperation();
455                         if (op.isWriteBufferOperation())
456                                 return (WriteBufferOperation *)(&op);
457                 }
458         }
459         return NULL;
460 }
461
462 void NodeOperationBuilder::add_input_buffers(NodeOperation * /*operation*/,
463                                              NodeOperationInput *input)
464 {
465         if (!input->isConnected())
466                 return;
467
468         NodeOperationOutput *output = input->getLink();
469         if (output->getOperation().isReadBufferOperation()) {
470                 /* input is already buffered, no need to add another */
471                 return;
472         }
473
474         /* this link will be replaced below */
475         removeInputLink(input);
476
477         /* check of other end already has write operation, otherwise add a new one */
478         WriteBufferOperation *writeoperation = find_attached_write_buffer_operation(output);
479         if (!writeoperation) {
480                 writeoperation = new WriteBufferOperation(output->getDataType());
481                 writeoperation->setbNodeTree(m_context->getbNodeTree());
482                 addOperation(writeoperation);
483
484                 addLink(output, writeoperation->getInputSocket(0));
485
486                 writeoperation->readResolutionFromInputSocket();
487         }
488
489         /* add readbuffer op for the input */
490         ReadBufferOperation *readoperation = new ReadBufferOperation(output->getDataType());
491         readoperation->setMemoryProxy(writeoperation->getMemoryProxy());
492         this->addOperation(readoperation);
493
494         addLink(readoperation->getOutputSocket(), input);
495
496         readoperation->readResolutionFromWriteBuffer();
497 }
498
499 void NodeOperationBuilder::add_output_buffers(NodeOperation *operation, NodeOperationOutput *output)
500 {
501         /* cache connected sockets, so we can safely remove links first before replacing them */
502         OpInputs targets = cache_output_links(output);
503         if (targets.empty())
504                 return;
505
506         WriteBufferOperation *writeOperation = NULL;
507         for (OpInputs::const_iterator it = targets.begin(); it != targets.end(); ++it) {
508                 NodeOperationInput *target = *it;
509
510                 /* try to find existing write buffer operation */
511                 if (target->getOperation().isWriteBufferOperation()) {
512                         BLI_assert(writeOperation == NULL); /* there should only be one write op connected */
513                         writeOperation = (WriteBufferOperation *)(&target->getOperation());
514                 }
515                 else {
516                         /* remove all links to other nodes */
517                         removeInputLink(target);
518                 }
519         }
520
521         /* if no write buffer operation exists yet, create a new one */
522         if (!writeOperation) {
523                 writeOperation = new WriteBufferOperation(operation->getOutputSocket()->getDataType());
524                 writeOperation->setbNodeTree(m_context->getbNodeTree());
525                 addOperation(writeOperation);
526
527                 addLink(output, writeOperation->getInputSocket(0));
528         }
529
530         writeOperation->readResolutionFromInputSocket();
531
532         /* add readbuffer op for every former connected input */
533         for (OpInputs::const_iterator it = targets.begin(); it != targets.end(); ++it) {
534                 NodeOperationInput *target = *it;
535                 if (&target->getOperation() == writeOperation)
536                         continue; /* skip existing write op links */
537
538                 ReadBufferOperation *readoperation = new ReadBufferOperation(operation->getOutputSocket()->getDataType());
539                 readoperation->setMemoryProxy(writeOperation->getMemoryProxy());
540                 addOperation(readoperation);
541
542                 addLink(readoperation->getOutputSocket(), target);
543
544                 readoperation->readResolutionFromWriteBuffer();
545         }
546 }
547
548 void NodeOperationBuilder::add_complex_operation_buffers()
549 {
550         /* note: complex ops and get cached here first, since adding operations
551          * will invalidate iterators over the main m_operations
552          */
553         Operations complex_ops;
554         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it)
555                 if ((*it)->isComplex())
556                         complex_ops.push_back(*it);
557
558         for (Operations::const_iterator it = complex_ops.begin(); it != complex_ops.end(); ++it) {
559                 NodeOperation *op = *it;
560
561                 DebugInfo::operation_read_write_buffer(op);
562
563                 for (int index = 0; index < op->getNumberOfInputSockets(); index++)
564                         add_input_buffers(op, op->getInputSocket(index));
565
566                 for (int index = 0; index < op->getNumberOfOutputSockets(); index++)
567                         add_output_buffers(op, op->getOutputSocket(index));
568         }
569 }
570
571 typedef std::set<NodeOperation*> Tags;
572
573 static void find_reachable_operations_recursive(Tags &reachable, NodeOperation *op)
574 {
575         if (reachable.find(op) != reachable.end())
576                 return;
577         reachable.insert(op);
578
579         for (int i = 0; i < op->getNumberOfInputSockets(); ++i) {
580                 NodeOperationInput *input = op->getInputSocket(i);
581                 if (input->isConnected())
582                         find_reachable_operations_recursive(reachable, &input->getLink()->getOperation());
583         }
584
585         /* associated write-buffer operations are executed as well */
586         if (op->isReadBufferOperation()) {
587                 ReadBufferOperation *read_op = (ReadBufferOperation *)op;
588                 MemoryProxy *memproxy = read_op->getMemoryProxy();
589                 find_reachable_operations_recursive(reachable, memproxy->getWriteBufferOperation());
590         }
591 }
592
593 void NodeOperationBuilder::prune_operations()
594 {
595         Tags reachable;
596         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it) {
597                 NodeOperation *op = *it;
598
599                 /* output operations are primary executed operations */
600                 if (op->isOutputOperation(m_context->isRendering()))
601                         find_reachable_operations_recursive(reachable, op);
602         }
603
604         /* delete unreachable operations */
605         Operations reachable_ops;
606         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it) {
607                 NodeOperation *op = *it;
608
609                 if (reachable.find(op) != reachable.end())
610                         reachable_ops.push_back(op);
611                 else
612                         delete op;
613         }
614         /* finally replace the operations list with the pruned list */
615         m_operations = reachable_ops;
616 }
617
618 /* topological (depth-first) sorting of operations */
619 static void sort_operations_recursive(NodeOperationBuilder::Operations &sorted, Tags &visited, NodeOperation *op)
620 {
621         if (visited.find(op) != visited.end())
622                 return;
623         visited.insert(op);
624
625         for (int i = 0; i < op->getNumberOfInputSockets(); ++i) {
626                 NodeOperationInput *input = op->getInputSocket(i);
627                 if (input->isConnected())
628                         sort_operations_recursive(sorted, visited, &input->getLink()->getOperation());
629         }
630
631         sorted.push_back(op);
632 }
633
634 void NodeOperationBuilder::sort_operations()
635 {
636         Operations sorted;
637         sorted.reserve(m_operations.size());
638         Tags visited;
639
640         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it)
641                 sort_operations_recursive(sorted, visited, *it);
642
643         m_operations = sorted;
644 }
645
646 static void add_group_operations_recursive(Tags &visited, NodeOperation *op, ExecutionGroup *group)
647 {
648         if (visited.find(op) != visited.end())
649                 return;
650         visited.insert(op);
651
652         if (!group->addOperation(op))
653                 return;
654
655         /* add all eligible input ops to the group */
656         for (int i = 0; i < op->getNumberOfInputSockets(); ++i) {
657                 NodeOperationInput *input = op->getInputSocket(i);
658                 if (input->isConnected())
659                         add_group_operations_recursive(visited, &input->getLink()->getOperation(), group);
660         }
661 }
662
663 ExecutionGroup *NodeOperationBuilder::make_group(NodeOperation *op)
664 {
665         ExecutionGroup *group = new ExecutionGroup();
666         m_groups.push_back(group);
667
668         Tags visited;
669         add_group_operations_recursive(visited, op, group);
670
671         return group;
672 }
673
674 void NodeOperationBuilder::group_operations()
675 {
676         for (Operations::const_iterator it = m_operations.begin(); it != m_operations.end(); ++it) {
677                 NodeOperation *op = *it;
678
679                 if (op->isOutputOperation(m_context->isRendering())) {
680                         ExecutionGroup *group = make_group(op);
681                         group->setOutputExecutionGroup(true);
682                 }
683
684                 /* add new groups for associated memory proxies where needed */
685                 if (op->isReadBufferOperation()) {
686                         ReadBufferOperation *read_op = (ReadBufferOperation *)op;
687                         MemoryProxy *memproxy = read_op->getMemoryProxy();
688
689                         if (memproxy->getExecutor() == NULL) {
690                                 ExecutionGroup *group = make_group(memproxy->getWriteBufferOperation());
691                                 memproxy->setExecutor(group);
692                         }
693                 }
694         }
695 }