Cuttlefish Dev Log / Scratch Notes
Dev Golf
hour- + long tasks under existing structures,
- assign meaning to charts and graphs (date / location / etc)
- this should be generic json-object making, yeah? / csv
- save tests as .json objects (optionally) develop program for reading
- overlay multiples, save images
- read-in csv as well, polymorphic for outputs
- generic wrap / unwrap data objects ...
- the force loop / layout (floop.js)
- wire length != zero
- better keepout-rectangles
- spawn new hunks at the mouse (floaters! things 'inside' of others!)
- how to do this all elegantly ... it's ah big redesign: take some hours, consider, maybe it's not so grand ?
- sys / save etc
- dates & times in metadata on list ...
- save systems w/ layout ??
- menu
- arrow keys to bump through items
Aspirational Dev
Things we want to see, extensions, etc,
Compound Types
I was thinking about the cf 'api' / workings today. At the moment there is this convoluted / poorly named system where the view messages the manager
with some similarely-but-not-well-named names. In the browser, these are actually hooked up through a messaging loop, to abstract in the same way in JS / CPP (remote) and / JS (remote). This was great for development, but there's another way to do it that might be much nicer for the future... which iiiis to retain the 'view' object, but (at the top level) attach each of it's core function (the eight) directly to the manager's reciprocal function, both cast as promises. Other views can call the same promises, but in those cases they're hooked to the view's messaging 'subsystem' ... put this note in the relevant repo.
For mixed up data types / polymorphism / I just had this idea this morning (more of a clarification) about compound types, and their wraps. Compounds, and Compound Arrays. This is maybe worth some time, and some drawing. I was considering the Squid/Protocol in the context of big serverside apis / the microservices trend. This future where people don't build eachother's software source, but people plug into eachother's operating hardware: where we collaboratively wire together one big operative graph. To mux big data objects, we can wire up / bundle compound data types, named. This is a serialization that rests on our core set of byte-structures, and a wrapping / unwrapping tool, that names things. For instance: a compound wrapper is a hunk having some set of inputs, and one output. The inputs are typed / named as is tradition. Some of these can even be other compound data types. The wrapper (sync / async) takes data off of its inputs, and wraps them into one serialized message, that has some known 'compound' structure... Like, a list of names, then data bits all serialized in key, with type-keys leading. In the compound array messages, we have an array of these compound types (this is the CSV type), where we have one list of names, arrays of these type-keyed objects.
This is a really nice thing that I would really want to see. Compounds should maybe have names, but should maybe just be id'd by their final roots: any ordered set of the-same types of data should be compatible with the same: that's really the data-type identifier... Noice.
... treating data like this allows complex, application / architecture specific representations to be used, but bundles interoperability into the core datatypes we ... likely all agree on. this is on the expectation that computing becomes more specialized as we progress, not less.
Better Error Paths
I do, really, want to use this as a wrench. The first component to that is to reconsider error- and logging paths. Though this might add some small delay, I can wrap the cuttlefish manager's loop in a try/catch loop (over per-hunk loops as well). I can write logs out to a floater that is associated with that def's core. Top level logs can come from the manager... bootstrap can catch very top level, those would be halting. Error paths should also default to logging to the console as well... and modifying both to change logging behaviour should be possible.
Past Logging (Completed or Forgotten Tasks)
Next Desires (2019-09)
I think that mostly I would like to focus on the potential of a KOA / Express cuttlefish & vfp package, to run local systems away from my active development.
Following this, probably the best things to do will be address things like type inconsistencies, and add better error pathing... Also, tickets like the network-level view, and starting to daydream about the interstitial routing layer, if it exists? That awkward link.
The Latest (2019-07)
I'm excited about this, so here's two images:
... many lines of js later, I can 'explode' hunk definitions, which means I can invert link relationships, so that I can visually explain nested network relationships in the graph. I've also simplified a lot of view objects, and have come up with this satisfying idea about recipes and atomic operations that I enjoy - that helps me a lot in patch loading, saving, etc - especially for big messy patches (I hope). I have also learned: UIs are a PITA.
Here's the 'unwrapped' link on an 'edgecase' link. This is like looking through a door: depending on the side you're on, the hinge is on the left... or the right... or, fittingly, tx and rx are rx and tx depending on from-where you take your UART names. The cable flips. Etc.
To wrap these things, I can compose 'recipes' from cuttlefish, to manipulate graphs, i.e. building routes and then organizing visual heirarchy accordingly:
Alright, here's the real moment: opening two-layers-of-links down into a (home for) an embedded instance.
Couple of things, at this point:
- browser performance ... starting to feel the lag, surely, many things could be done to improve this. another day.
- video capture makes thigs ~ 4x slower. graphics? idk
OK, after some more testing with genuinely time sensitive systems, I'm not interested in doing planning in the browser anymore. That means that I want better tools to work intimately between node and the browser. To start, I refined my route building tool, so that I can quickly push events down layers. Here's a clip of my ui element (an arrow pad) connecting to a planning element that needs to run ~ 100Hz loops to hardware:
w/r/t force layout
I've re-written much of the display code. the 'view' hunk, that does rendering work, is about 2500 lines all together at this point, which is a pretty yikes-level of code for me.
That said, it's basically the whole project's workhorse - which is interesting in itself. This is one thing I'm definitely excited about for the project: the contexts / managers are very simple - the whole thing has this pretty straightforward 'hind brain' that is just pushing memory around (inside of contexts) and pushing bytes around (over the network).
This might sound like a boast, but I guess this is kind of similar to writing a programming language ... at the base, the instruction set is really small. The syntax, rules, compiling, etc of the language is big, cumbersome, and messy.
The most recent add is to do with making visual sense of nested programs. In order to do this, I've broken up what were previously single <div>
elements into collections of them... i.e. inputs, outputs, and state each get a box to live within. This causes some more complexity to the view program because I have to keep track of these handfuls of html elements, and then there's this: I use the same mechanism to 'explode' hunk-definitions, and to 'wrap' (i.e. links) around others (i.e. views). This is really handy, because it lets me accurately draw link->link representations 'across' the link divide, and lets me 'unwrap' a pattern that was very visually cluttered: this common 'hourglass' structure (i.e. feedback).
So, I wrote this all in a rush, in what felt like a javascript bender, and now I'm trying to catalogue accurately what-all happened, to track and hopefully resolve. What I'm working towards is bringing force layout back into the picture, because (as is easy to tell when opening a big program) these things are hot hot messes, and I'm not up to storing positions exactly yet, because that sounds counter to the point.
Here's the things I'd like to be able to do:
- blow-up definitions, having inputs, outputs, and cores be in potentially different places.
- have 'exploded' elements, probably most often used to 'unwrap,' say, a data pipe around a link, (to unconfuse the hourglass)
- using exploded elements, pin outputs to the left, and inputs to the right. this in particular to show a link 'across' a view
- wrap a link around a view
- resize views, and other 'native' elements.
Other tricks
- the message board should perhaps belong only to the top-level view, and should be easily hidden
Considering Force Layout
There'll be a handful of tricks to make this work out well
-> a good update model -> this thing can also check our 'rulesets' per the heirarchy, for links and views and whatnot -> that's a kick (topology changes and checks), and a tick (simulation step) -> integrating this with manual movements ... -> the floop should probably only run at the top level ... but subroutines will be nice per view because -> we will have / want to adjust the size of sub-views (in a bubbling kind of way) from the bottom-up... i.e. we really want to avoid overlap, etc
Probably a good first step is a run through the .def tool, to invent a decent API for moving, exploding, gathering, etc. making 'edge' types. Three types...
- fixed
- exploded (just str8 up kapow, but maybe as a rule just blow up the input ?))
- wrapped
- unwrapped
For each object, I'll want to re-write the .deg api so that .deg.inputs.moveto() is a viable function, etc ... such that when I'm running through the sim, every free entity can be one of these ->
floater.moveto(x,y) // floater.fix(x,y)
and those can be walked as nodes ...
dragging objects ? just kill the sim I guess ? or fix it during the sim ... dragging objects should use the same floater.moveto() setup, or a .moveby()
then I also have to handle native-element resizing ... so .wrap objects need to react to that...
and then there's this question about how to do it heirarchichally - or all at once, in one floop ? maybe all at once is easier ...
A good way to set rules ... like, what's fixed etc. I.E. consider -> the ears of a link expand 'around' a program, pushing the view definition 'out' - how will these 'forces' be connected? Just .set the view to match, let them float? Pin the left to 0,0 and float the right? Then take the lower level constraints as fact? nice.
Traversing Heirarchy
I am having a feeling that I am going about this poorly. I should remember that I am here to build a tool, not get into semantics about UI and write nice APIs for myself. Alas, black holes abound.
I need to get to ponyo.
I want to open programs with heirarchy. I'm close to doing this but I have to suss out this relationship between the top-layer view and the lower-level(s) view(s).
I think my issue is that I'm just kind of trammeling through this without any strong ideas about how the larger structures will work. Lower level things feel OK so far.
On a lack of heirarchy sweeping... I am curious to keep the recepies to a minimum. Surely, I'll save systems with heirarchy. When I'm loading them, I can be intelligent about how to make the dives. Once I load a layer, any links (with programs defined beneath them) I can interrogate. I can write a routine to hunt (using link-traversal) for connected views. If those views are available, I'll connect those views to the link (definition?) and use those to traverse another layer. Then I can do program->link.view->defs... as a reference structure. This means that, yes, I should attach (to link defs) view (hunks). Which may get somewhat confusing. However!
This handles program traversal. What else I need is to wrap on (hah) my implementation of these force layout loops. Critically, I need events to 'bubbble' through them.
My missing link, and maybe the only thing I haven't really api'd well, is the resize-ability of elements. As a hack, I wrote this into my domtools tool ... which leaves things somewhat ambiguious. I need to call a resize: (1) on ui events, and (2) internally - i.e. a view(hunk) or any other native(hunk) should be able to call .requestResize(w,h). Any time the resulting element.resize(w,h) fn is called, a change should be triggered: this layer's floop should run from alpha(1) (making sure it has a bigger r, now). This floop, I think, will (in a few cases) also call it's domain's .requestResize(w,h) event - so, that's the link through.
In the same vein, a lower level view has a key difference from the higher level view, in that an element (floater) moving around inside, should not be able to move past the 'wall' of the view's plane. I can probably do this in a few ways, (1) by just writing in limits... but I will have to differentiate between lower-level views and higher-level views. I can also modify lower- and upper- level floops to incorporate (or not) a force at the edges. This would be for an investigation into the floop's internals (i.e. the D3 force library).
Nested Floops and Resize Transoms
Floaters, each view runs a floop, using resize events to pass between heirarchies.
A good exercise at this point would be to describe how / what level you'd like to organize API stuff. Whomst has which links, functions, references. Particularely, use the cases of resizing. Try to go fast.
The floop should be able to .reset() during topology changes, and .restart() for others - drags, etc. Perhaps a third, or perhaps during .restart, to recalculate sizes (but not adding or rm'ing nodes or links) - especially useful for a resize event.
The resize, also, (at the moment, terribly buried in the domtools) often wants to call multiple other events - one for the floater that contains it, one for the native hunk it represents (if it's a native block). The floater that contains it (confusing, also, bc the floater might be a link / wrap) should call it's view's .restart() fn.
The Simulation Implementation
The simulation definitely does things, but at the moment there are two big flaws. Hopefully I can just get rid of these instead of writing about them. Primarily: (1) circles are not actually a good collision metric and (2) 'links' at the moment are drawn as if they originate from the center of each representation.
This is (maybe) worth documenting. A meta-note is that, doing this again, I would probably draw all of my svgs with D3 as well. Here's a few links:
force layout in d3 cola - different solver rectangular collision more rectangles
A few of these examples are from D3 v3, while the newest version is D3 v4 - particularely, this update changed how force layout works. So.
D3 is continually being-very-cool. Here's another neat trick: a quadtree implementation that makes collision-detections not computationally cumbersome.
Great, so I took a look also at the source (der), here, in particular, the collision module is (obvious reasons) what I want to modify. I swapped the .forceCollision() d3 function in for a module, and got to writing...
First note I came here to write down is that I can eliminate the prepare() function that is walked before the 'visit' (ok, quadtree words). This is because I am keeping my own radius (or equivalent) data right on the nodes. Integrated systems get +10 pts.
So, cool, and I did the same thing for the link force - this time modifying the x-terms it uses to offset left/write of the floaters' bounding box. Last rites here is writing in individual link y-offsets (at write-time) to use in lining up in the y-direction.
Boundary Conditions
The top-level space doesn't exactly have anything to prevent floaters. It would be great to do some graph organization routines that 'pin' parts of these things down.
Speeding Up, and UI Events
- don't need to redraw every link every time. Link redraws should be triggered per object, like (floater).links.forEach((link) => {link.redraw()})
- floaters use messy organization to check- and recheck- state when asked to move, these things could be improved
- floater children could be smarter. i.e. consider the view's hunk being an item in an otherwise-link floater's bag ... this mismatch is what makes the resize callbacks troublesome.
Jake's Notes on Writing UIs
I've taken a handful of passes through righting this monster. Like any other project, at the nth pass I finally feel as though I am properly equipped to design what I've made. Prior to this project I had never designed a UI, but in some senses the crux of the whole project is about UI - more broadly, about representation, and putting handles on complex problems. In a few steps, this was about coherence in computing model, and then in messages, and then, in the UI, in state tracking and drawing.
OK, so what have I learned?
UIs are kind of like state machines... one big tripping point was in deciding where to keep 'truth' about what state was what. The DOM is obviously a strong model for this, but it can be cumbersome to access (i.e. I'm thinking of .value keys on input objects, etc) - this is the reason we see JS littered with 'getters' and 'setters' - a getter can parse out of whatever bedrock storage, and a setter can be written to modify requisite dom elements, even if those dom elemengts change.
Abstraction is nice, but requires real foresight, and can be a black hole. This is also a statement about truthyness / bedrocks... elegance can be tough.
UIs should have a runtime model: or ideas about events. Lots is going on - often we are processing mouse clicks, drags, values, buttons, etc. We don't know what is going to happen in what order. In this case, there is this kind of 'global' problem of running the force-layout simulation. This thing needs to be poked at the right times, stopped at the right times, etc. s
Access-to-parents-and-children. This is kind of true all the time, but is especially true w/r/t the runtime / events mentioned above. It's really nice to be able to bump up and down heirarchy to call parent's events, etc. Children should be given handles to their parents, parents should have good handles on their children. In some cases, (i.e. defs, floaters, and floater-elements), two graphs share common elements, at varying levels in their structures.
For a case study, my current implementation (as of June 8 2019) of resizing native-hunk-dom-elements, is a hot mess. Resizing code is local to the element itself, and fires a few callbacks that aren't really tracked anywhere... Just poke through that on the next pass, to get a sense for what I mean.
On Usefulness / Heavy-Handedness
One choice that I made early on was to try to leave the 'view' repreesented as 'a hunk' running in cuttlefish. I still like this, and it gives me a good way to express 'manager messages' traversing down heirarchy. It also lets me use 'recipes' to compose UIs from links / etc. It's eating my own dogfood.
However, it clutters the view with ... well ... hunk representations of views and managers. There's some temptation to get rid of these things. Primarily because they don't actually add any value to us when we're using this thing to compose programs.
Another take is to give hunks direct access to their managers, and do some invisible routing. I.E. rather than composing a route 'down' to a link, across a few links, pass a message to link->manager->link->manager->link .. via indexed messages. I.E. a manager receives from the link 'message for hunk_ind | msg' and messages are passed along this way.
With infinite time, this might be a compelling re-write. It would also allow me to localize hunk-descriptions, and state change calls. I.E. links could pass messages to managers 'message for hunk_ind | stchreq:ind:value' and hunks can have a handle to rx / handle these as well. Super-locality would be nice, would potentially make managers really simple. And responses could probably be source-routed back out.
So, that's tempting. However, I think this can go forwards to flesh out hunks etc, and I can be set up to make such a re-write actually pretty easy if enough evidence presents itself for it. Always learning.
Before Wrapping to Ponyo
Making this into a useful tool is going to be a slog. Has been a slog. The best way to develop it is to do more and more messy and difficult stuff with it. To wrap and move on, I need to:
(1) complete force layout using rectangular collisions and appropriately-placed links. (2) save ponyoDevTool.json - a nesting patch - and load it. this involves finalizing link.js / link.cpp 'api' - i.e. open/closed state, tossing messages when closed (?or?), and the reciprocal-index state value.
From there, I think the most important work to do is developing the hunks themselves. I should try to imagine a useful set, draw the programs I think I will want to use for mocontrol (and the instron is a good case), and get to work. Getting work out to other users and programmers, also big.