On 15/01/04, Daniel Rogers wrote: Oh. I love all these graphs of nodes and ops in emails. They are hard to do, aren't they? You raise a lot good points in all of your emails. > > ok. Yes, what I am thinking is different, it a subtle but very > important point. An image is a bounded region, composed of pixels. The > primary problem I see with your approach is that you are trying to treat > images like ops. Very early on (at gimpcon) you and pippin both made it > very clear that edges should be images and nodes should be ops. I > didn't quite understand the power of that statement when you made it, > but I went with it anyway, trusting that you two were right. > > Now though, you seem to be doing something completely different. Let > try and explain: No. I havent changed. I still am saying the same thing. I still say that there are two basic things in the system. Things that operate on data (nodes, or ops) and data. (I know, I used to make a distinction between nodes and ops, I dont anymore. Take node == op in the following) Now images are one example of a kind of data that passes through the graph. There are other kinds of data that you will want to pass through the graph as well... scalar data, sound data, geometry data, animation data, anything. So its good to separate out these two types of things so it is not confusing. The system determines dependencies for data to be passed along a graph. Any data can be passed with the right set of nodes. This is just what maya does, with its dependency graph, its just one big fat property connecting system. Basically data moves through the graph by passing from property to property, so at its most fundamental level it is really a graph of property connections: those are the primary thing. The node connections are there too but really those node connections are there and determined by virture of how the various properties are connected. The real lines in the graph are all property connection lines. At that low level they are all the same. Properties are connected to other properties. Input properties can have only one line going into them, output properties can have several lines coming out of them. Nodes are containers for output and input properties and so have a set of connections determined between them too once you specify which properties in the system are attached to each other. So there are several kinds of views of this graph of dependencies. One is a low level properties view where you put in all the properties and look at the lines between properties. Another is where you look at nodes and you see the connections between nodes only (these were determined by the properties underneath though) . Then yet another view is where you have containers of nodes and these are kinds of graphs where you can put all your ops into a container then you can "bubble up" (I used to call this lifting properties, nowadays I see C# calls it bubbling) some children node properties and call these the input properties and output properties of the overall graph. Anyway here's a simple example where the distinction between nodes and properties is clearer: property names: - <-----output C + <-----input - - <-----output0, output1 B + + <-----input0, input1 - <-----output A + + + <-----input0, input1, input2 This example has three nodes: A, B, and C - stands for an output property + stands for an input property C has 1 input property and 1 output property B has 2 input properties and 2 output properties. A has 3 input properties and 1 output property Each property in the above (either + or -) could correspond to a gobject style property, so you might access them with the usual g_object_get and g_object_set calls. Output properties are read-only. Input properties are read/write. Properties may be the usual ints, floats, strings, arrays, or more complicated data like images, sound data, geometry. Any kind of property that can be passed though the g_object_set, get property mechanisms and defined and plopped into a GValue, is fair game to get involved with the game. Now here's one way to connect up the dependencies in the graph: - C + \ - - B + + | - A + + + In the picture the second output property of B (output1) is connected to the single input property of C, and the single output property of A is connected to the second input property (input1) of B. For the connections between the nodes, the dependencies just looks like this: C \ B | A A is connected to B and B is connected to C. This doesnt show the full set of properties involved or which input properties are connected up to which output properties, it just shows a picture of what connections exist between nodes. Finally a sort of different graph showing all the property connection dependencies you might draw like this: - | <---- C + \ - / | <--- B + + | - /|\ <--- A + + + This is the graph you might traverse if you were going to visit all the properties involved. So the nodes-only view doesnt show a lot of details and doesnt display every property. But the property connection views show all the data that is involved (every input and output to the nodes, both images and other inputs) and where the data will move exactly along the graph. You should traverse along property lines to determine dependencies because that way you dont end up asking a node to do any work related to outputting one of its outputs you are not interested in. Now lets think about what happens when we ask C to give us its output property. Data will be passed along property lines in an order that is determined by the graph of dependencies, but we are free to set up things and traverse the properties and nodes in any way we want to set that up. The nodes and properties dont have any traversal logic, they implement a "visitable" interface so we can write visitors to traverse the graph however we want. We can traverse it at the node level sometimes or at the property level, depending on what what we want to do. So we have an evaluation manager set up everything after a call to the root property, it does preliminary propagation of inherited and synthesized attributes down and up the graph and queues up whatever multi-threading tasks there will be, and descriptions of those tasks. Of course these tasks are arranged so that they are done in a way consistent with the dependency graph, but that is really the only restriction. I think that leaves a lot of freedom to decide how to break up your evaluation and calls on the nodes and properties for them to do their thing. If you want to install a completely different evaluation manager from whatever gegl one we have and with a different execution model you can do that easily (within the limits of the dependency graph again, and callbacks on the nodes and properties to their "evaluate" methods.) Like it is great that gegl can be used on top of whatever image library you want to put behind it, not just ours. Suppose you have a lot of image code that doesnt use any of gegl's approach to color models, sample models, tiles, images or whatever, then you can still hook up your system and get some graph processing mileage out of it. (Dont make me extend GeglImage or GeglImageOp, that brings in all your color models, and tiles, and data buffers... Ill pull this car over...). All you'd have to do is write your wrapper nodes, define your properties, figure out how to serialize out your properties, and there you have it. You can call your own image processing routines during evaluate. Anyway an evaluation now goes something like this: - | <---- C: this is a call to evaluate(C, "output") + \ <-g_object_get(B, "output1",...); g_object_set(C, "input",...); - / | <--- B: this is a call to evaluate(B, "output1") + + | <-g_object_get(A, "output",...); g_object_set(B, "input1",...); - /|\ <--- A: this is a call to evaluate(A, "output"); + + + So the data is passed along as properties are traversed. Some of the lines above represent lines along which image data is moving , and some lines represent other data traveling along Each node is where several input properties are combined into a single output property. This way gives you a nice convenient place to do conversion of properties that dont match if you have written g_value_transforms for them. It also shows you all the settable things in your graph, images, scalars, strings, whatever. You can choose to only draw the nodes and connections between nodes and not show properties or all properties if you want, or you choose to only ever use image producing ops. (Make all your ops inherit from ImageOp if you want). But if you make it so that any parameter can come from a property directly or from a property of another node you will make it so you can hook gegl up to interesting things. Fine if you never do, then you only ever see GeglImageOp and its subclasses, which are a specific set of ops that are tuned to pass gegl image data along the graph. > This example doesn't do your api justice however, because you have the > problem that sometimes, your property is an image, and sometimes your > property is an op, and they are treated differently I call this a plus. Every property comes directly from being set on the node, or from the output of another node. Both are possible. Im just trying to emphasize that the property, node and data passing framework should be kept minimal and general at the highest level. I think that you can arrange the image passing subsystem of ops in most anyway you want really within this framework. Okay. Enough. I better shut up now. Calvin