mr-rodgers / coil Goto Github PK
View Code? Open in Web Editor NEWLicense: The Unlicense
License: The Unlicense
In order to offer the enhanced syntax for data bindings from #1, coil needs a way to keep track of active cross-property bindings, currently modelled as asyncio tasks. These tasks must necessarily be managed for the duration of the binding. This could have been alternatively handled with a context manager doing something like this:
async with coil.bind((host, prop)) as bindctx:
bindctx.feed(bind(other_host, prop))
as this is also able to manage the lifetime of the data binding. While being easier to implement, this is still a more cumbersome syntax than proposed in #1. The more natural and readable syntax from #1 is preferred; therefore, a runtime must be created.
The coil runtime is an object which does the following:
task1 = asyncio.create_task(...)
task2 = asyncio.create_task(...)
async with coil.runtime() as rt:
rt.register(task1, "some-task")
rt.register(task2, "some-task", source=(host, prop))
assert rt.find("some-task") is task1
assert rt.find("some-task", source=(host, prop)) is task2
rt.forget(task1)
assert rt.find("some-task") is None
assert task1.done()
assert task2.done()
coil.Runtime()
class should be provided.
coil.runtime()
should return a Runtime()
instance:
ensure=True
(the default), it should create a new runtime and return itensure=False
, it should raise a RuntimeError
.Currently, bindings can be tailed into another, essentially allowing a 1-way bind from a bindable property into another. However, the syntax for doing this is particularly cumbersome:
import asyncio
from coil import bind, bindableclass
from coil.utils import tail
@bindableclass
class Box:
value: int = 0
async def main():
orig = Box()
shadow = Box()
task = tail(bind((orig, "value")), into=bind((shadow, "value"), readonly=False))
for i in range(10):
orig.value = i
await asyncio.sleep(0.5)
print(shadow.value)
task.cancel()
await task
if __name__ == '__main__':
asyncio.run(main())
This 1-way bind is done using the tail(...)
function, which has a somewhat finicky interface:
bind(...)
function; the bound values could also have been constructed from the property descriptor: tail(Box.value.bind(orig), into=Box.value.bind(shadow, readonly=False))
. That improves readability, but it's still clunky.coil should introduce an abstraction layer for tail bindings:
# under the hood
task = tail(bind((orig, "value")), into=bind((shadow, "value"), readonly=False))
...
task.cancel()
await task
# abstraction
async with coil.runtime():
...
shadow.value = bind((orig, "value"))
...
The abstraction allows to create a 1-way binding by assigning a coil-bound value to a bindable property. This binding lasts as long as shadow.value
is not reassigned.
In order to manage this tail task in the background, however, coil will require a runtime of some kind. That runtime would need to run for the entire duration of the application (or for as long as these bindings are useful), and one of its functions will be to manage the tasks that are required by this one way binding.
The runtime is not part of this issue, however (see #2).
So far we have only considered 1-way bindings which are modelled by a tail(...)
usage. The abstraction above can be extended to support 2-way bindings, by simply assigning an appropriate bind(...)
result:
shadow.value = bind((orig, "value"), readonly=False)
This will, however, require some changes to support:
TwoWayBound
(which setting readonly=False
returns). This is currently made difficult, because bind(...)
actually returns the same type under the hood for Bound
and TwoWayBound
, and sort of relies on the type checker to enforce the readonly
argument.Binding
into Binding
and TwoWayBinding
.
Binding
should provide no .set(...)
functionTwoWayBinding
should subclass Binding
to add itBound
and TwoWayBound
should be @runtime_checkable
DataEvent["source"]
to DataEvent["source_event"]
DataEvent["source"]
which tracks the Bound
which the event comes fromnotify_subscribers()
so that it ignores events which have the same bound value in its source chainBound
, then the 1-way tail must be created and tracked with the runtime.TwoWayBound
, then a 2-way bind is simulated by creating tails in opposite directions, and tracking them with the runtimecoil.tail
Currently, some framework operations are performed asynchronously (see #1), by wrapping them in tasks which are managed by a runtime. The problem with this, is that due to the synchronous context under which these operations are triggered (mainly member assignments/deletions), even if these operations are performed in an asyncio context, code which uses them has no way of knowing exactly when such operations have truly taken effect.
One such example is the clearing of tail bindings. Standard syntax for both setting and clearing tail binding background tasks are necessarily synchronous, since they rely on Python object assignment:
@coil.bindableclass
class Box:
value: int = 0
async def main():
async with runtime():
source = Box()
target = Box()
# tails values from source into target
target.value = Box.value.bind(source)
# stops tailing values from source into target
# there is a slight delay before this takes effect
target.value = Box.value.bind(target)
The correct initial linking of the binding usually takes place immediately. This is because the binding is fed by an event stream under the hood, and all of the necessary setup (registration of event hooks, event stream construction, creation of task for consuming event stream and applying the binding) takes place before the synchronous assignment operation returns.
The reverse operation however, is another story. Since bindings are managed by asyncio tasks which consume a value's event stream and applies changes to the binding target, and since task cancellations are asynchronous, a complete cancellation of the task cannot be guaranteed during a synchronous assignment operation. What's more, due to the semantics of this operation in the Python language, there is no way to give feedback to the developer on that task's cancellation through this assignment operation.
The unfortunate consequence then, is that a developer may clear a binding, but have no way to predict when it is safe again to write a new value into the attribute. The framework needs to provide such a facility to the developer, however broad.
A library function should be added, which can wait until all of it's pending task evictions are done processing. In other words, it waits for all tasks which have as of yet been discarded by the framework to fully cancel. This will provide the developer with a way to wait for the unwinding of tail bindings, before proceeding the setting new values on the target attribute.
The part of code that concerns itself with this is the runtime. Indeed, the background tasks which manage the tail bindings are in turn managed by whichever Runtime
is active at the time when the assignment is made. Such a function will need to get the current runtime, and wait for all of its pending cancellations to complete before returning.
RuntimeError
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.