summaryrefslogtreecommitdiff
path: root/src
AgeCommit message (Collapse)Author
2023-05-11modules/*: remove use of static default setupsVito Caputo
There was a time when it made sense for context creates needing setups but not receiving them to still be functional with some sane defaults. But with recursive settings, we really shouldn't ever have orphaned nested module uses unreachable by a proper setup. So let's just get rid of this fallback, and exclusively rely on the baked setups provided by the .setup() methods. They still have preferred defaults, and the proper setup production machinery is what should be responsible for applying those at runtime where they may also be overridden or otherwise influenced.
2023-05-11til: add setup assert in til_module_create_context()Vito Caputo
If the module has a .setup() method, it should be able to just blindly expect a valid baked setup to come in @ context create. Let's enforce that here, and let the module code providing setup hooks just go forth and access the setup assuming it's at least present and as valid as their setup method produces.
2023-05-11modules/rtv: add basic log_channels= settingVito Caputo
This introduces a boolean style log_channels= setting for enabling logging of channel settings on channel switch. It might be nice to change this to accept stdout/stderr/fdnum as the setting instead of always directing at stderr. This also doesn't capture the seed state so it's not exactly logging everything needed to reproduce wholly what is being shown. Some compositions depend more on rand than others, so it matters at varying degrees. It'd be nice for settings syntax to have some global syntax supported where a seed can always be embedded to be loaded. Introducing such things as global settings to the settings syntax is a pending TODO item... right now the only way to load seed state is at startup passed to main as --seed=. That's not gonna cut it long-term. This is an easy big step in the right direction though. Trying to make sense of what's on-screen from the truncated captions is impossible. Even if the captions wrapped the settings, it would be tricky to catch the settings without recording the output or screenshotting. This also immediately makes me wonder about the voting system for rtv where we log settings of favorites... then roll those into playlists.
2023-05-11til: teach til_randomize_setup() about nested settingsVito Caputo
This probably needs more work, but it's good enough for now
2023-05-11setup: label anon nested settings as subscriptsVito Caputo
It'll be perfectly normal to turn bare-value settings int nested instances. In such scenarios we don't have a static spec-supplied key for the label, so let's just generate a label like a C-style array subscript for such instances.
2023-05-11til_settings: helper for labeling positional instancesVito Caputo
When there's a bare-value setting turned into a nested settings instance, there's no key onhand for labeling the instance. But such nameless settings are basically array elements only positionally accessed. This helper produces labels in that style for such settings by taking the container settings label and adding [$idx] to the end. These labels aren't really used as more than a debugging aid at the moment. But module contexts already have paths in main, and it seems like these settings instance labels will likely become the name components in constructing those paths.
2023-05-11til_settings: recursive til_settings_as_arg()Vito Caputo
Currently in rototiller the only (de)serialization format of settings is the args strings, so this is a rather critical piece to get in for recursive settings to really be usable. This is a quick and dirty implementation utilizing open_memstream() which despite being POSIX has spotty support (I don't think MacOS has it for instance). So that's probably something to rip out in the future. Nonetheless, this moves things forward and works fine on GNU.
2023-05-11til_settings: make use of til_setting_desc_t.containerVito Caputo
This commit pivots everything over to using desc->container as the target settings instance when adding settings, as well as actually assigning the settings container @ desc create. Given nothing is actually triggering settings heirarchies yet (no specs set as_nested_settings) this shouldn't actually result in any realized functional difference, yet. The settings pointer being placed in desc->container should be identical to what was getting used before.
2023-05-11til_settings: don't check values on nested settingsVito Caputo
There needs to be more flexibility in the value checking enforcement. This is just a quick blunt-hammer fix to not trip over nested settings values which will be huge undeterministic messes vs. what's likely just a set of simple presets in the spec.values[] The individual leaf settings will still be checked if they specify values. So this change just stops even bothering to check unless the setting is a leaf. This area needs more work in general, see comments. For instance right now we can't just pass in arbitrary floats for settings which list float values, not if that arbitrary float isn't a member of the list. In some circumstances that's the right thing to do, as in the module can only work with the presets. But most of the time, the module would be perfectly happy with e.g. foo=.3379 with foo's spec.values[] = { .01, .1, .25, .75, 1} so the check fails. That's dumb, and interferes with the creative process when you're just poking different numbers into the settings to see what happens. TODO
2023-05-11til_settings: add til_settings_get_count()Vito Caputo
Getter for accessing til_settings_t.num With recursive settings modules will start using settings instances to represent things like lists of module names. (modules/compose::layers for instance) For those cases they'll want to know the number of settings in the instance before iterating across getting their values positionally.
2023-05-11til_settings: introduce til_setting_spec_t concept vs. descVito Caputo
For recursive settings the individual setting being described needs to get added to a potentially different settings instance than the one being operated on at the top of the current setup_func phase. The settings instance being passed around for a setup_func to operate on is constified, mainly to try ensure modules don't start directly mucking with the settings. They're supposed to just describe what they want next and iterate back and forth, with the front-end creating the settings from the returned descs however is appropriate, eventually building up the settings to completion. But since it's the setup_func that decides which settings instance is appropriate for containing the setting.. at some point it must associate a settings instance with the desc it's producing, one that is going to be necessarily written to. So here I'm just turning the existing til_setting_desc_t to a "spec", unchanged. And introducing a new til_setting_desc_t embedding the spec, accompanied by a non-const til_settings_t* "container". Now what setup_funcs use to express settings are a spec, otherwise identically to before. Instead of cloning a desc to allocate it for returning to the front-end, the desc is created from a spec with the target settings instance passed in. This turns the desc step where we take a constified settings instance and cast it into a non-const a more formal act of going from spec->desc, binding the spec to a specific settings instance. It will also serve to isolate that hacky cast to a til_settings function, and all the accessors of til_setting_desc_t needing to operate on the containing settings instance can just do so. As of this commit, the container pointer is just sitting in the desc_t but isn't being made use of or even assigned yet. This is just to minimize the amount of churn happening in this otherwise mostly mechanical and sprawling commit. There's also been some small changes surrounding the desc generators and plumbing of the settings instance where there previously wasn't any. It's unclear to me if desc generators will stay desc generators or turn into spec generators. For now those are mostly just used by the drm_fb stuff anyways, modules haven't made use of them, so they can stay a little crufty harmlessly for now.
2023-05-11til_settings: rework setting get/add for bare valuesVito Caputo
The core thing here is rather than turning a bare value into a key as I was doing before - we just leave the bare value as a bare value and its setting must be located positionally via get_value_by_idx since there's no key. Existing callers that used to get_key() positionally now get_value_by_idx() positionally all the same, except it's the value instead of the key. This is mostly done for things like the module or fb name at the front of a settings instance. The impetus for this change is partially just cosmetic/ergonomics, but it's also rather strange for what's really a key-less value to be treated as a value-less key. It was also awkward to talk/reason about on the road to recursive settings where bare values would be supported as a standalone settings instance if properly escaped... This also adds unescaping of keys, and adds a dependency on the somewhat linux-specific open_memstream() which may need changing in the future (see comments).
2023-05-11setup: constify settings passed to setup_funcVito Caputo
setup_func isn't formally defined for libtil, but setup_interactively() defacto establishes it and til_module_t.setup() reflects the same signature and calling convention except with til_settings_t constified. This change makes them all consistent in this regard, but there should probably be a formal typedef added for the function. The reason for constifying this is I don't want setup functions directly manipulating the settings instance. In the case of rototiller::setup_interactively() we ensure the stdio-based interactive setup is always the side doing the manipulation of the settings. For a libtil-user like glimmer, it's slightly different beast with GTK+ in the loop, but by preventing the setup_funcs from messing directly with the settings (instead having to describe what they want done iteratively), the front-end always gets its opportunity to maintain its state while doing the described things. Of course, this is mostly a lie, and within libtil the constified til_settings_t gets cast away to modify it in places. But keeping that limited to within libtil is tolerable IMO. We just don't want to see such casts in module code.
2023-05-11main: quote shown flags/argsVito Caputo
Preparatory commit for recursive settings going cray-cray with escaping. At least single-quote these so it's directly copy-and-pasteable into a shell prompt.
2023-05-11til_settings: add til_setting_desc_t.as_nested_settingsVito Caputo
When this is set, the setting is itself to be a settings instance that the frontend must create and place in the relevant til_setting_t.value_as_nested_settings. This commit implements that frontend portion in setup_interactively() for the rototiller frontend. No setup_func() yet attempts to make use of this stuff. There's probably more change needed before that can happen, specifically the setup_func() likely must always produce a til_settings_t* to indicate which settings instance is currently relevant to the frontend. Without setup_func() telling the frontend, the frontend has basically no other way of knowing when the backend setup_func() has moved up/down the heirarchy at the current iteration.
2023-05-11til_settings: add til_settings_t.value_as_nested_settingsVito Caputo
Preparatory commit for settings hierarchies.
2023-05-10til_settings: label til_settings_t instancesVito Caputo
This adds a mandatory label string to til_setttings_new() and updates call sites accordingly. For now the root-level settings created by main.c are simply named "module" and "video" respectively. Any nested settings creations on behalf of modules will be labeled using the module's name the settings are being created for use with. This might evolve with time, for now it's just a minimum churn kind of decision. I can see it changing such that the top-level settings also become labeled by the module/video driver name rather than the obtuse "module" "video" strings. How these will be leveraged is unclear presently. At the least it'll be nice to have a label for debugging til_settings_t heirarchies once recursive settings support lands. In a sense this is a preparatory commit for that work. But I could see the labels ending up in serialization contents as markup/syntactic sugar just to self-document things as well. There might also be a need to address til_settings_t instances in the settings heirarchy, which may be something like a "label/label/label/label" path style thing - though there'd be a need to deal with name collisions in that approach. I'm just thinking a bit about how knobs will become addressed when those become a real thing. The settings label heirarchy might be the convenient place to name everything in a tree, which knobs could then inherit their parent paths from under which their respective knob labels will reside. For the whole name collision issue there could just be some builtin settings keys for overriding the automatic module name labeling, something like: --module=compose,layers=checkers\,label=first\,fill_module=shapes:checkers\,label=second\,fill_module=shapes would result in: /module/first/shapes /module/second/shapes or in a world where the root settings weren't just named "module" and "video": /compose/first/shapes /compose/second/shapes then if there were knobs under checkers and shapes, say checkers had a "foo" knob and checkers had a "bar" knob, they'd be under .knobs in each directory: /compose/first/.knobs/foo /compose/first/shapes/.knobs/bar /compose/second/.knobs/foo /compose/second/shapes/.knobs/bar something along those lines, and of course if compose had knobs they'd be under /compose/.knobs This is just a brain dump and will surely all change before implemented.
2023-05-09modules/compose: pass n_cpus to layers/texture context createsVito Caputo
Existing code was passing 0 which turns into the number of cores/threads. That's fine when compose isn't running nested in an already threaded render, but falls down in something like checkers w/fill_module=compose since checkers is already threading. But when checkers creates its fill_module context, it's careful to pass 1 for n_cpus to prevent that kind of thing. With this change that no longer falls apart.
2023-03-13modules/rocket: mv modules/rocket modules/rktVito Caputo
Finishes build/fs part of modules/rocket->modules/rkt rename started in previous commit.
2023-03-13modules/rocket: %s/rocket/rkt/gVito Caputo
It's annoying to have the til module called rocket, and the sync tracker protocol/library called rocket, so let's at least differentiate it in code/comments/textual discussion. Plus this results in shorter module context paths i.e.: /rkt:scene /rkt/compose/drizzle:rain /rkt/compose/drizzle:viscosity /rkt/compose/plato:spin_rate /rkt/compose/plato:orbit_rate vs. /rocket:scene etc... These names are shown in the editor, and they'll tend to be long but let's at least get the root name down to three chars this way. A rename of the files and build system update will come in a subsequent commit
2023-02-23libs/rocket: bump submodule and enable TCP_NODELAYVito Caputo
Upstream merged my TCP_NODELAY PR, so let's get it in rototiller. Note I'm blindly setting USE_NODELAY now, but it might actually break the build for win32 - still need to test that.
2023-02-06til: get rid of the partial knobs implementationVito Caputo
With taps more or less fully implemented, it seems appropriate to get rid of the stubbed out knobs for now. Taps don't express the same things about range and usage knobs aspired towards, but they don't preclude adding such things either. But it seems clear that the way knobs were stubbed won't be complementing taps as things stand currently to add those aspects.
2023-02-06modules/meta2d: add some taps for controlling visible bandVito Caputo
There should probably be others for the ball radiuses, and colors
2023-01-22modules/flui2d: add taps for viscosity,diffusion,decayVito Caputo
this needs more work to really be useful... clockstep should be tapped, there should probably be a T tap to control the emitters' cycle im doubtful how useful this module will generally be though. It really needs interactions with other things, like fluid going around pixbounce
2023-01-22modules/drizzle: add taps for viscosity,rainfallVito Caputo
Just some obvious taps... Note the actual usable range for viscosity is quite small, like no greater than .05 really works. There still needs to be some way to describe bounds on the taps, that or normalizing things to always be 0-1 or -1..+1 kind of thing, and expecting the modules or the tap api to map those to the sane ranges. Just leave everything raw for now, wiring up the taps at least opens up experimentation and getting a feel for what makes most sense.
2023-01-22modules/blinds: add taps for T,step,countVito Caputo
Part of me wants to give the blinds arbitrary angle instead of the vert/horiz options. But part of the beauty of blinds is the jaggy-free aliasing-free sharp edges by virtue of always being orthonormal using whole pixels. Maybe in the future there could be a orientation setting where you pick horiz/vert/angular. Then only when angular does it get a theta tap and use angled blinds with anti-aliased imperfect edges...
2023-01-21modules/rocket: implement GNU Rocket integrationVito Caputo
This adds a rudimentary but functional rocket module for sequencing "tapped" variables in rototiller modules according to a timeline via GNU Rocket editors. Currently this only supports a single seq_module= as a setting which will be used for rendering. Any tapped variables present in the nested modules under seq_module will be available for sequencing, and should automatically appear in a connected rocket editor. If you specify connect=off then rocket sync tracks will be read from the filesystem if present. It's a bit clumsy as-is due to how the GNU Rocket library handles this currently. There's a "base" label concept for the virtual rocket device, and the tracks are intended to be files in a directory named using that base= setting. The way you create those track files is by triggering a remote export from the editor while connected. The location of the directory is relative to the cwd of the rototiller process, and you can't specify absolute paths as the base= setting to be explicit about where things go. The setting isn't really a path, as that's not what the library wants it to be. It's an area in need of improvement. In any case, as long as you start with the same base= setting, from the same CWD, as when you did the remote export, you can re-run with connect=off and the exported tracks will be used automagically and things should replay without the editor connected. If you start with connect=on, which is the default, you need to have the editor already running. Otherwise the rocket module will fail @ context create, and you'll get a confusing error about being unable to allocate memory. This is just for now, the context create needs to start returning an errno instead of just the context pointer so the error messages can be more informative now that context create may be doing complicated things like connecting to sockets. Another thing to improve is probably having the module just reconnect periodically if connect=on but it failed @ context create. It could just start anyways and not fail the context create at all there, and just start working once you get the editor online. That'd be a better user experience. This is a good first step regardless...
2023-01-21modules/rocket: preliminary rocket moduleVito Caputo
This just stubs out a rocket meta module that renders with another module. Future commits will integrate GNU Rocket here. When recursive settings formally lands you'll be able to nest as much settings content as necessary for the underlying module used, as part of the rocket settings. That should enable describing stuff like complex compose scenarios for rocket to sequence.
2023-01-21til_stream: introduce til_stream_hooks_t et alVito Caputo
There needs to be a way for a meta module like rocket to take ownership of pipes immediately upon instantiation. Since the pipes are created on demand as they become tapped by the modules using htem, the simplest way to do this is to register some callbacks with the ability to intercept the pipe creation in terms of ownership and driving tap control etc. This commit forms a minimal implementation of that, with the ability to have a single intercepter hooked into a given stream. It's a first-come-first-served situation, but that should suffice for now since the rocket meta module would be the entrypoint for such constructions. It then calls into another module to produce on the stream, after it'll already have its hooks registered. There might be a need for stacking hooks to let multiple modules control pipes. GNU Rocket for instance only deals with floats/doubles, and doesn't make it particularly easy to work on higher order concepts like say orbiting a vector around a point spatially. It might make sense to allow compositing of editors where there's rocket controlling the simple floats, and another doing dimensional/spatial stuff, with separate stacked meta modules accomodating those editors. But that's putting the cart before the horse, let's do the stupid simple thing for now and see what this is like.
2023-01-21til_stream: teardown pipes when the driving_tap's owner matchesVito Caputo
The driving tap's owner and pipe's owner are decoupled. When tearing down an owner from a stream, any pipes its taps are driving should also just go away. Otherwise its taps could linger on pipes it doesn't own, which would be a UAF bug. If the pipe is still needed, it'll just get recreated by another tap. So there's a small perf hit, but this shouldn't be a continuos kind of occurrence.
2023-01-21til_stream: minor cosmetic naming/comments fixesVito Caputo
Some clarifications
2023-01-21til_stream: add til_stream_pipe_set_driving_tap()Vito Caputo
2023-01-21til_{tap,stream}: introduce til_tap_t.inactiveVito Caputo
When a driving tap becomes inactive, til_stream_tap() should be able to notice and replace the driver. An example driving tap becoming inactive would be a GNU Rocket track that once had keys in it, but then had them all deleted. This should set the inactive flag so the tap's automation can take over. This gives the user at the Rocket editor the ability to both take over from the tap automation and surrender control back, by populating vs. emptying the respective track.
2023-01-21til_stream: introduce stream iterator et alVito Caputo
In order to implement something like a rocket module there needs to be a way to iterate the pipes in the stream, and take owernship of them when not already owned by rocket. The way rocket's API works is you lookup tracks by name at runtime. The rocket module will be a meta module that calls into another module for rendering, arbitrarily configured via a rocket setting a la checkers::fill_module. So it won't be until the underlying modules do some rendering that their taps get their respective pipes established in the stream. Then the rocket module can look at all the pipes and for any it doesn't own yet, it can get the tracks for those names and take ownership while stowing the track handle in owner_foo for the pipe. While iterating all the pipes, the pipes already owned will have the tracks readily available which can produce the values to stick in the tap. Something like that anyways, the til_stream_t api changes in this commit are all preparatory for a rocket module.
2023-01-21til_stream: comment fix, and assert/error on mismatchVito Caputo
Just clarify some verbiage, and actually assert type+n_elems match. Note mismatch also fallsthrough to an -EINVAL just in case asserts() have been compiled out (-DNDEBUG).
2023-01-21til_stream: add a second void* to til_stream_pipe_tVito Caputo
It seems likely that pipe owners will need not only a way to differentiate themselves via the owner pointer, but also somewhere to register a pipe-specific reference. There probably needs to be a result pointer added for storing the owner_foo when the owner taps, so the owner can make use of it.
2023-01-21til_tap: introduce til_tap_t.ownerVito Caputo
We need a way to identify owners of taps when cleaning up their containing contexts, especially once they're hanging off streams.
2023-01-20til: pass module to .context_create()/til_module_context_new()Vito Caputo
Let's make it so til_module_context_t as returned from til_module_context_new() can immediately be freed via til_module_context_free(). Previously it was only after the context propagated out to til_module_context_create() that it could be freed that way, as that was where the module member was being assigned. With this change, and wiring up the module pointer into til_module_t.create_context() as well for convenient providing to til_module_context_new(), til_module_t.create_context() error paths can easily cleanup via `return til_module_context_free()` But this does require the til_module_t.destroy_context() be able to safely handle partially constructed contexts, since the mid-create failure freeing won't necessarily have all the members initialized. There will probably be some NULL derefs to fix up, but at least the contexts are zero-initialized @ new.
2023-01-20libs/rocket: add GNU Rocket submoduleVito Caputo
Preparatory commit for experimenting with a GNU Rocket integration for controlling the stream pipes on a timeline. Since rocket doesn't support things like arbitrary strings, it's not a natural fit for rototiller where the obvious thing would be to describe scene compositions as settings strings as if you were invoking rototiller. But a temporary hack might be to just tell a rocket module up-front all the scenes as settings strings you provide to its setup. Those get assigned numeric identifiers, then rocket tracks can control when they come on/off numerically. It just requires describing all the scenes up front rather than in the pattern editor which is less than ideal. Being able to experiment with this half-ass solution may prove useful anyways, and shouldn't be too much work.
2023-01-17til_fb: don't dereference NULL fragment opsVito Caputo
For strictly logical fragments (e.g. tiled fragmenters) there won't be any ops, and that's even documented in the comments. But the snapshot and reclaim functoins were assuming the ops would be non-NULL. Snapshot in particular trips on this assumption when a module snapshots a subfragment, like drizzle in montage. I'm surprised I haven't encountered this crash before...
2023-01-12modules/compose: fix segfault introduced by 83e41dVito Caputo
It was assumed (n_modules - n_overlayable) would give the number of non-overlayable modules appropriate as base layers. But with the skipping of hermetic and experimental modules the base_idx could be out of reach leaving layers NULL after the loop, which will segfault later when strlen() assumes it's non-NULL. This commit does the simple thing and also counts the unusable modules to subtract from those eligible for base layers along with n_overlayable.
2023-01-11til_{stream,tap}: add GPL headersVito Caputo
Just some banal paperwork...
2023-01-11til: omit experimental modules from til_module_setup()Vito Caputo
Don't make experimental modules available to the regular/potentially-interactive setup routine. There should be a flag like --experimental to generally enable these.
2023-01-11modules/montage: omit experimental and hermetic modulesVito Caputo
As with the other composite modules, if --experimental happens this will need adjustment to honor it. For now let's just prevent things from breaking when those modules start appearing.
2023-01-11modules/compose: omit experimental and hermetic modulesVito Caputo
This only omits the modules from the random layers Note the texture_values list is enumerated in compose_setup, so there's no corresponding change needed there. It might make sense to change that to a runtime-discovered list though, I think that was done in the pre-flags era.
2023-01-11modules/rtv: skip hermetic/experimental modules for "all"Vito Caputo
This allows explicit listing of such modules as channels, while protecting the automagic/defaults scenario. If there's a future --experimental flag or such added, then the TIL_MODULE_EXPERIMENTAL check will have to become conditional on it.
2023-01-11main: experimenting with ANSI codes for --print-pipesVito Caputo
This turns --print-pipes into a more top-like display. Redirect the FPS on stderr somewhere else to get less flickering e.g. 2>/dev/null pipes print to stdout.
2023-01-11til: intrdouce a couple new module flagsVito Caputo
TIL_MODULE_HERMETIC: There's likely to be some new modules that are more orchestration style components having external runtime dependencies. Think stuff like a sequencer talking to GNU Rocket, or something that plays back pattern data from external files. Those would need a GNU Rocket process to talk to somewhere, or input pattern file paths. So they shouldn't participate in stuff like random rtv shows unless they have some fallbacks for when the dependencies are unavailable. For pattern data it's realistic to include some builtin patterns to fallback on, but we're not there yet. So this flag when specified should opt out of things like rtv or checkers fill_module random selections. TIL_MODULE_EXPERIMENTAL: Theres no current way to have knowingly unstable/unfinished modules available in-tree for development/collaboration purposes without having them also make stuff like rtv unstable. Modules having this flag set should be excluded from random inclusion without a --experimental or some such runtime flag specified. This commit only assigns values and names for the flags, it doesn't implement anything.
2023-01-11* turn til_fb_fragment_t.stream into a discrete parameterVito Caputo
This was mostly done out of convenience at the expense of turning the fragment struct into more of a junk drawer. But properly cleaning up owned stream pipes on context destroy makes the inappropriateness of being part of til_fb_fragment_t glaringly apparent. Now the stream is just a separate thing passed to context create, with a reference kept in the context for use throughout. Cleanup of the owned pipes on the stream supplied to context create is automagic when the context gets destroyed. Note that despite there being a stream in the module context, the stream to use is still supplied to all the rendering family functions (prepare/render/finish) and it's the passed-in stream which should be used by these functions. This is done to support the possibility of switching out the stream frame-to-frame, which may be interesting. Imagine doing things like a latent stream and a future stream and switching between them on the fly for instance. If there's a sequencing composite module, it could flip between multiple sets of tracks or jump around multiple streams with the visuals immediately flipping accordingly. This should fix the --print-pipes crashing issues caused by lack of cleanup when contexts were removed (like rtv does so often).
2023-01-11main,til_args: employ the stream, add --print-pipesVito Caputo
This is a rudimentary integration of the new til_stream_t into rototiller. If the stream is going to continue living in til_fb_fragment_t, the fragmenters and other nested frame scenarios likely need to be updated to copy the stream through to make the pipes available to the nested renders. --print-pipes dumps the values found at the pipes' driver taps to stdout on every frame. Right now there's no way to externally write these values, but with --print-pipes you can already see where things are going and it's a nice visibility tool for tapped variables in modules. Only stars and plato tap variables presently, but that will improve.
© All Rights Reserved