Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI zoom in/out, need to squash one remaining bug #231

Merged
merged 4 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:

- run: |
mk python-release owner=vkottler \
repo=runtimepy version=4.5.0
repo=runtimepy version=4.5.1
if: |
matrix.python-version == '3.11'
&& matrix.system == 'ubuntu-latest'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
=====================================
generator=datazen
version=3.1.4
hash=48a3836c8cab67b925fe019f3db34c8d
hash=c9e8e0a4b1765408663652c02a32e75e
=====================================
-->

# runtimepy ([4.5.0](https://pypi.org/project/runtimepy/))
# runtimepy ([4.5.1](https://pypi.org/project/runtimepy/))

[![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
Expand Down
2 changes: 1 addition & 1 deletion local/variables/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
major: 4
minor: 5
patch: 0
patch: 1
entry: runtimepy
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__"

[project]
name = "runtimepy"
version = "4.5.0"
version = "4.5.1"
description = "A framework for implementing Python services."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
4 changes: 2 additions & 2 deletions runtimepy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =====================================
# generator=datazen
# version=3.1.4
# hash=9539398893e7a420adf97d118b6bcabf
# hash=4c10d97db8d952d1b242acfcfd80abb4
# =====================================

"""
Expand All @@ -10,7 +10,7 @@

DESCRIPTION = "A framework for implementing Python services."
PKG_NAME = "runtimepy"
VERSION = "4.5.0"
VERSION = "4.5.1"

# runtimepy-specific content.
METRICS_NAME = "metrics"
Expand Down
19 changes: 13 additions & 6 deletions runtimepy/channel/environment/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ def __setitem__(self, key: _RegistryKey, value: ChannelValue) -> None:
"""Mapping-set interface."""
return self.set(key, value)

def set(self, key: _RegistryKey, value: ChannelValue) -> None:
def set(
self, key: _RegistryKey, value: ChannelValue, scaled: bool = True
) -> None:
"""Attempt to set an arbitrary channel value."""

# Set a field value if this key maps to a bit-field.
if self.fields.has_field(key):
assert not isinstance(value, float)
self.fields.set(key, value)
self.fields.set(key, value, scaled=scaled)
return

chan, enum = self[key]
Expand Down Expand Up @@ -155,7 +157,10 @@ def set(self, key: _RegistryKey, value: ChannelValue) -> None:
)

# Assign the value to the channel.
chan.raw.scaled = value # type: ignore
if scaled:
chan.raw.scaled = value # type: ignore
else:
chan.raw.value = value # type: ignore

def apply(self, values: ValueMap) -> None:
"""Apply a map of values to the environment."""
Expand All @@ -172,17 +177,19 @@ def values(self, resolve_enum: bool = True) -> ValueMap:
}

def value(
self, key: _RegistryKey, resolve_enum: bool = True
self, key: _RegistryKey, resolve_enum: bool = True, scaled: bool = True
) -> ChannelValue:
"""Attempt to get a channel's current value."""

# Get the value from a field if this key points to a bit-field.
if self.fields.has_field(key):
return self.fields.get(key, resolve_enum=resolve_enum)
return self.fields.get(
key, resolve_enum=resolve_enum, scaled=scaled
)

chan, enum = self[key]

value: ChannelValue = chan.raw.scaled
value: ChannelValue = chan.raw.scaled if scaled else chan.raw.value

# Resolve enumeration values to strings.
if enum is not None and resolve_enum:
Expand Down
3 changes: 3 additions & 0 deletions runtimepy/data/js/classes/Plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ class Plot {
let plotButton = document.getElementById("runtimepy-plot-button");
if (plotButton) {
this.canvas.onclick = (event) => { plotButton.click(); };
this.canvas.onwheel = this.onWheel.bind(this);
}
}

onWheel(event) { this.plotMessage({"wheelDelta" : event.wheelDelta}); }

plotMessage(data, param) { this.worker.toWorker({"plot" : data}, param); }

messageBase() {
Expand Down
11 changes: 11 additions & 0 deletions runtimepy/data/js/classes/PlotDrawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,15 @@ class PlotDrawer {

this.wglp.viewport(0, 0, this.canvas.width, this.canvas.height);
}

updateDepth(wheelDelta) {
for (let name in this.channels) {
let chan = this.channels[name];

/* Make configurable at some point? */
chan.buffer.bumpCapacity(wheelDelta > 0);

chan.draw(this.lines[name]);
}
}
}
11 changes: 11 additions & 0 deletions runtimepy/data/js/classes/PlotManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ class PlotManager {
}
}

updateDepth(name, wheelDelta) {
if (name in this.drawers) {
this.drawers[name].updateDepth(wheelDelta);
}
}

async handleMessage(data) {
let name = data["name"];

Expand All @@ -56,6 +62,11 @@ class PlotManager {
this.plots[name] = data["canvas"];
}

/* Handle scroll events. */
if ("wheelDelta" in data) {
this.updateDepth(name, data["wheelDelta"]);
}

/* Handle size updates. */
if ("width" in data && "height" in data) {
let canvas = this.plots[name];
Expand Down
69 changes: 53 additions & 16 deletions runtimepy/data/js/classes/PointBuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@ class PointBuffer {
this.values = [];
this.timestamps = [];

this.elements = 0;
this.updateCapacity(capacity);
}

bumpCapacity(bumpUp) {
/*
* need persistent settings for scroll behavior? (configurable?)
*/
let scale_factor = 1.25;
let newCapacity = Math.max(16, bumpUp ? this.capacity * scale_factor
: this.capacity / scale_factor);
this.updateCapacity(Math.round(newCapacity));
}

reset() {
this.head = 0;
this.tail = 0;
Expand All @@ -16,20 +27,32 @@ class PointBuffer {
}

updateCapacity(capacity) {
this.capacity = capacity;
this.reset();
/* Copy existing values. */
let points = [];

let newValues = new Array(this.capacity);
let newTimestamps = new Array(this.capacity);
let count = Math.min(this.elements, capacity, this.capacity);
if (count > 0) {
let startIdx = this.head;

/* Copy existing values. */
for (let i = this.values.length; i < this.capacity; i++) {
newValues[i] = this.values[i];
newTimestamps[i] = this.timestamps[i];
/* If the buffer is getting smaller, advance the buffer index forward. */
if (count < this.elements) {
startIdx += this.elements - count;
startIdx = startIdx % this.capacity;
}

for (let i = 0; i < count - 1; i++) {
let idx = (startIdx + i) % this.capacity;
points.push([ this.values[idx], this.timestamps[idx] ]);
}
}

this.values = newValues;
this.timestamps = newTimestamps;
/* Reset state and re-ingest points. */
this.reset();
this.capacity = capacity;
this.values = new Array(this.capacity);
this.timestamps = new Array(this.capacity);

this.ingest(points);
}

ingest(points) {
Expand Down Expand Up @@ -62,15 +85,29 @@ class PointBuffer {
*/

let slope = 2 / (newestTimestamp - oldestTimestamp);

/* Build array of plot-able timestamp X values. */
let times = [];
let idx = oldestIdx;
while (idx != newestIdx) {

if (slope > 0) {
/* Build array of plot-able timestamp X values. */
let idx = oldestIdx;

while (idx != newestIdx) {
times.push(((this.timestamps[idx] - oldestTimestamp) * slope) - 1);
idx = this.incrIndex(idx);
}
times.push(((this.timestamps[idx] - oldestTimestamp) * slope) - 1);
idx = this.incrIndex(idx);

} else {
/* need to root-cause this off-by-one issue */
console.log(`${newestIdx}, ${oldestIdx}, ${this.elements}`);
console.log(slope);

let idx = oldestIdx;
while (idx != newestIdx) {
times.push(oldestTimestamp)
idx = this.incrIndex(idx);
}
}
times.push(((this.timestamps[idx] - oldestTimestamp) * slope) - 1);

return times;
}
Expand Down
14 changes: 14 additions & 0 deletions runtimepy/primitives/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
"""

# built-in
from contextlib import contextmanager as _contextmanager
from copy import copy as _copy
from math import isclose as _isclose
from typing import BinaryIO as _BinaryIO
from typing import Callable as _Callable
from typing import Generic as _Generic
from typing import Iterator as _Iterator
from typing import TypeVar as _TypeVar

# third-party
Expand Down Expand Up @@ -104,6 +106,18 @@ def remove_callback(self, callback_id: int) -> bool:
del self.callbacks[callback_id]
return result

@_contextmanager
def callback(
self, callback: PrimitiveChangeCallaback[T]
) -> _Iterator[None]:
"""Register a callback as a managed context."""

ident = self.register_callback(callback)
try:
yield
finally:
self.remove_callback(ident)

@property
def value(self) -> T:
"""Obtain the underlying value."""
Expand Down
15 changes: 13 additions & 2 deletions runtimepy/primitives/field/manager/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,17 @@ def add(self, fields: _BitFields) -> int:

return index

def set(self, key: _RegistryKey, value: _Union[int, bool, str]) -> None:
def set(
self,
key: _RegistryKey,
value: _Union[int, bool, str],
scaled: bool = True,
) -> None:
"""Set a value of a field."""

# Bit fields don't support scaling.
del scaled

field = self[key]

if isinstance(value, str):
Expand All @@ -125,10 +133,13 @@ def set(self, key: _RegistryKey, value: _Union[int, bool, str]) -> None:
field(int(value))

def get(
self, key: _RegistryKey, resolve_enum: bool = True
self, key: _RegistryKey, resolve_enum: bool = True, scaled: bool = True
) -> _Union[int, bool, str]:
"""Get the value of a field."""

# Bit fields don't support scaling.
del scaled

field = self[key]
value: _Union[int, str] = field()

Expand Down
2 changes: 2 additions & 0 deletions tests/channel/environment/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,5 @@ def test_channel_environment_basic():
assert env.get_int(4) is not None

verify_missing_keys(env)

env.set("float.1", 1.0, scaled=False)
1 change: 1 addition & 0 deletions tests/data/valid/connection_arbiter/runtimepy_http.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ app:
config:
experimental: true
foo: bar
xdg_fragment: "wave1,hide-tabs,hide-channels/wave1:sin,cos"

clients:
- factory: runtimepy_http
Expand Down
16 changes: 16 additions & 0 deletions tests/primitives/test_bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,19 @@ def test_bool_basic():

prim.clear()
assert copied()

call_count = 0

def change_cb(_: bool, __: bool) -> None:
"""A sample callback."""
nonlocal call_count
call_count += 1

with prim.callback(change_cb):
prim.toggle()
prim.toggle()

prim.toggle()
prim.toggle()

assert call_count == 2