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

WIP #223

Merged
merged 1 commit into from
May 7, 2024
Merged

WIP #223

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.4.0
repo=runtimepy version=4.4.1
if: |
matrix.python-version == '3.11'
&& matrix.system == 'ubuntu-latest'
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ mklocal
docs
compile_commands.json
src
*.webm
*.log
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=72952b0b2e4342ce27726da3c02bc379
hash=696c64ad3b4f9680a238a934819b4c89
=====================================
-->

# runtimepy ([4.4.0](https://pypi.org/project/runtimepy/))
# runtimepy ([4.4.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 config
36 changes: 36 additions & 0 deletions local/arbiter/compress_and_gif.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash

set -e

SCALE_ARGS=()

create_gif() {
set -x
ffmpeg -y -i "$1" "${SCALE_ARGS[@]}" "${1%.*}.gif"
set +x
}

# https://askubuntu.com/questions/1440589/how-to-compress-videos-properly-with-webm
COMPRESS_ARGS=(-c:v libvpx-vp9 -b:v 0 -crf 48 -deadline best)
COMPRESS_ARGS+=(-row-mt 1 -b:a 96k -ac 2)

compress_and_gif() {
# Two pass compression.
COMPRESSED="compressed-$1"

set -x
ffmpeg -i "$1" "${COMPRESS_ARGS[@]}" \
-pass 1 -an -f null /dev/null
ffmpeg -y -i "$1" "${COMPRESS_ARGS[@]}" \
-pass 2 -c:a libopus "$COMPRESSED"
set +x

# Create GIF.
create_gif "$COMPRESSED"
}

# Scale gif down.
SCALE_ARGS+=(-vf scale="832:-1")
create_gif "$1"

# compress_and_gif "$1"
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: 4
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.4.0"
version = "4.4.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=ba303dd05d632963067eb7020aeb9f0e
# hash=28647a8e43d3a46f378e9bb06c1c6d30
# =====================================

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

DESCRIPTION = "A framework for implementing Python services."
PKG_NAME = "runtimepy"
VERSION = "4.4.0"
VERSION = "4.4.1"

# runtimepy-specific content.
METRICS_NAME = "metrics"
Expand Down
4 changes: 4 additions & 0 deletions runtimepy/data/browser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
config:
# Attempt to open a browser automatically.
xdg_open_http: true
2 changes: 1 addition & 1 deletion runtimepy/data/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ body > :first-child {
background-color: var(--bs-secondary-bg-subtle);
}

#ui-plot {
.click-plot {
cursor: pointer;
}

Expand Down
39 changes: 37 additions & 2 deletions runtimepy/data/js/classes/PlotDrawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ class PlotDrawer {
/* Point managers for individual channels. */
this.channels = {};

/* need to make this an N entity for multiple channels */
/* Line objects by channel name. */
this.lines = {};

/* Keep track of x-axis bounds for each channel. */
this.oldestTimestamps = {};
this.newestTimestamps = {};
}

update() { this.wglp.update(); }
Expand All @@ -25,20 +29,51 @@ class PlotDrawer {
this.newLine(name);
} else {
delete this.lines[name];
delete this.oldestTimestamps[name];
delete this.newestTimestamps[name];
this.channels[name].buffer.reset();
}

this.updateLines();
}

handlePoints(points) {
/* Handle ingesting new point data. */
for (const key in points) {
if (key in this.states && this.states[key]) {
/* Add point manager and create line for plotted channel. */
if (!(key in this.channels)) {
this.channels[key] = new PointManager();
}
if (key in this.lines) {
this.channels[key].handlePoints(points[key], this.lines[key]);
let result = this.channels[key].handlePoints(points[key]);

/* Update timestamp tracking. */
this.oldestTimestamps[key] = result[0];
this.newestTimestamps[key] = result[1];
}
}
}

/* Compute x-axis bounds (min and max timestamps). */
let minTimestamp = null;
let maxTimestamp = null;
for (const key in this.oldestTimestamps) {
let oldest = this.oldestTimestamps[key];
let newest = this.newestTimestamps[key];
if (minTimestamp == null || oldest < minTimestamp) {
minTimestamp = oldest;
}
if (maxTimestamp == null || newest > maxTimestamp) {
maxTimestamp = newest;
}
}

/* Re-draw all lines. */
if (minTimestamp != null && maxTimestamp != null) {
for (const key in this.channels) {
if (key in this.lines) {
this.channels[key].draw(this.lines[key], minTimestamp, maxTimestamp);
}
}
}
Expand Down
33 changes: 22 additions & 11 deletions runtimepy/data/js/classes/PointBuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class PointBuffer {
this.head = 0;
this.tail = 0;
this.elements = 0;
this.oldestTimestamp = null;
this.newestTimestamp = null;
}

updateCapacity(capacity) {
Expand All @@ -30,7 +32,7 @@ class PointBuffer {
this.timestamps = newTimestamps;
}

ingest(points, line) {
ingest(points) {
for (let point of points) {
/* Store point. */
this.values[this.tail] = point[0];
Expand All @@ -48,16 +50,18 @@ class PointBuffer {
}
}

this.draw(line);
/* Update tracking of oldest and newest point timestamps. */
this.oldestTimestamp = this.timestamps[this.head];
this.newestTimestamp = this.timestamps[this.newestIdx()];
}

normalizeTimestamps(newestIdx, oldestIdx) {
normalizeTimestamps(newestIdx, oldestIdx, oldestTimestamp, newestTimestamp) {
/*
* Determine slope+offset so each timestamp can be mapped to (-1,1)
* domain.
*/
let oldestTimestamp = this.timestamps[oldestIdx];
let slope = 2 / (this.timestamps[newestIdx] - oldestTimestamp);

let slope = 2 / (newestTimestamp - oldestTimestamp);

/* Build array of plot-able timestamp X values. */
let times = [];
Expand Down Expand Up @@ -113,21 +117,28 @@ class PointBuffer {

incrIndex(val) { return (val + 1) % this.capacity; }

draw(line) {
newestIdx() {
let result = this.tail - 1;
if (result < 0) {
result = this.capacity - 1;
}
return result;
}

draw(line, oldestTimestamp, newestTimestamp) {
/* Need at least two points to draw a line. */
if (this.elements < 2) {
return;
}

/* Find indices for oldest and newest points. */
let oldestIdx = this.head;
let newestIdx = this.tail - 1;
if (newestIdx < 0) {
newestIdx = this.capacity - 1;
}

/* Build arrays of plot-able (normalized) timestamps and values. */
let times = this.normalizeTimestamps(newestIdx, oldestIdx);
let newestIdx = this.newestIdx();

let times = this.normalizeTimestamps(newestIdx, oldestIdx, oldestTimestamp,
newestTimestamp);
let values = this.normalizeValues(newestIdx, oldestIdx);

/* Set points. */
Expand Down
14 changes: 10 additions & 4 deletions runtimepy/data/js/classes/PointManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ class PointManager {
this.color = new WebglPlotBundle.ColorRGBA(Math.random(), Math.random(),
Math.random(), 1);

/* How should capacity be controlled? */
this.buffer = new PointBuffer(256);
/* How should capacity be controlled? Try to find UI element (probably
* needs to be passed in). */
this.buffer = new PointBuffer(512);
}

draw(line) { this.buffer.draw(line); }
draw(line, minTimestamp, maxTimestamp) {
this.buffer.draw(line, minTimestamp, maxTimestamp);
}

handlePoints(points, line) { this.buffer.ingest(points, line); }
handlePoints(points) {
this.buffer.ingest(points);
return [ this.buffer.oldestTimestamp, this.buffer.newestTimestamp ];
}
}
4 changes: 4 additions & 0 deletions runtimepy/data/server_dev.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
includes:
- package://runtimepy/dummy_load.yaml
- package://runtimepy/browser.yaml

config:
localhost: true
Expand All @@ -15,5 +16,8 @@ config:
# This is the default.
# html_method: runtimepy.net.server.app.channel_environments

# For a simple demo.
xdg_fragment: "wave1,hide-tabs,hide-channels/wave1:sin,cos"

port_overrides:
runtimepy_http_server: 8000
34 changes: 34 additions & 0 deletions runtimepy/net/server/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,47 @@
"""

# built-in
from contextlib import suppress
from importlib import import_module as _import_module
from typing import Any

# internal
from runtimepy.net.arbiter.info import AppInfo
from runtimepy.net.server import RuntimepyServerConnection
from runtimepy.net.server.app.create import config_param, create_app
from runtimepy.subprocess import spawn_exec
from runtimepy.util import import_str_and_item


async def launch_browser(app: AppInfo) -> None:
"""
Attempts to launch browser windows/tabs if any 'http_server' server ports
are configured.
"""

# Launch browser based on config option.
if config_param(app, "xdg_open_http", False):

port: Any
for port in app.config["root"]["ports"]: # type: ignore
if "http_server" in port["name"]:
# URI parameters.
hostname = config_param(app, "xdg_host", "localhost")

# Assemble URI.
uri = f"http://{hostname}:{port['port']}/"

# Add a fragment if one was specified.
fragment = config_param(app, "xdg_fragment", "")
if fragment:
uri += "#" + fragment

with suppress(FileNotFoundError):
await app.stack.enter_async_context(
spawn_exec("xdg-open", uri)
)


async def setup(app: AppInfo) -> int:
"""Perform server application setup steps."""

Expand All @@ -27,4 +59,6 @@ async def setup(app: AppInfo) -> int:
app, getattr(_import_module(module), method)
)

await launch_browser(app)

return 0
2 changes: 1 addition & 1 deletion runtimepy/net/server/app/env/tab/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,5 +255,5 @@ def compose(self, parent: Element) -> None:
tag="canvas",
id=self.get_id("plot"),
parent=div(parent=container, class_str="w-100 h-100 border-start"),
class_str="w-100 h-100",
class_str="w-100 h-100 click-plot",
)
1 change: 1 addition & 0 deletions tests/commands/test_arbiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ def test_arbiter_command_advanced():
args = base + [str(resource("connection_arbiter", f"{entry}.yaml"))]
if not is_windows():
args.append("dummy_load")
args.append("browser")

assert runtimepy_main(args) == 0