Open emdash opened 3 years ago
another pattern that libraries could try:
// my_lib.ud
type Output: ....;
type StatefulType: UninitializedStatefulType | InitializedStatefulType;
type UninitializedStatefulType: {
field state: "uninitialized";
method initialize() -> Initialized {
// tell downstream to allocate resources
}
};
type InitializedStatefulType: {
field state: "initialized";
field my_resource: DownstreamResource;
field my_resource: DownstreamResource;
method frobulate() {
self.my_resource....;
};
};
// my_script.ud
import my_lib;
input { field data: [Int], my_lib_state: field needs_init: my_lib.StatefulType};
// This idiom be factored into a template
match in.needs_init.state {
case "uninitialized" -> in.needs_init.init();
case "initialized" -> in.needs_init.frobulate();
}
This is for when a script is intended for a streaming context, and we wants to do some one-time initialization.
Concretely, it's needed so that uDash scripts can re-use resources between frames, like images, patterns, fonts, etc. If we know we're reloading, we can instruct the renderer to clear its cache, otherwise, the resource IDs from the previous frame are still valid -- so we can re-use the hash we computed last time, but we can elide the side-effect that allocated it.
Consider:
On the first iteration, creating the color objects should produce output that allocates resources. On subsequent iterations, they should not.
Initially
Thereafter
Here's an idea for a friendly-looking construct.
The idea is that the side effects inside of
initially
execute only the first iteration, while the effects insidethereafter
(which is optional) appear on subsequent iterations.Question:
initially
introduce bindings in the enclosing scope?first
is probably too likely to clash with identifiers, as wouldbegin
andinit
.Really, it's about avoiding having to "close the loop" around uDLang in some very common cases. There are a few reasons for wanting to do this, but the main one is that it makes it hard to factor initialization logic into libraries, since they need would need knowledge of the input type (the libraries should know nothing about the input).
Maybe the bigger question is whether uDLang should be used to manipulate stateful resources downstream in the first place. I'm not sure, but if we can't, it makes it hard for uDashboard to work efficiently with cairo's existing API, and would probably cause problems for an OpenGL backend as well.
If we bite the bullet and "close the loop", we can generate resource IDs downstream and fold them into the next iteration of the state. This works, but it feels an onerous burden to force this pattern on the user:
The most conservative approach is to restrict initially to the top-level of the script, allow it to appear only once, perhaps also require precede all other IO if it's used.
Regardless, if this construct is added, a flag should to the caller so that it can control which branch is run, for testing and debugging purposes in the one-shot case.
Here's an even simpler way to implement it, we just provide a variable binding which is set by the implementation:
This would be
true
in the one-shot case by default (but changable with a flag), and in the streaming case it is set to true on first iteration, and false afterwards. Care must be taken such that things likeinput
andoutput
directives can't appear inside either construct.I can't decide which I prefer. The first construct would force all this one-time initialization into the user's own code. The second version would favor hiding such behavior in library functions. I can see the pros and cons of each. I can also see the
sys
record being a generally useful way for a script to be reactive to its environment. As long as it only changes between iterations, and not during, this shouldn't break the language.