use isahc::prelude::*;
use serde_json::{json, Value};
use std::{
fmt::Debug,
path::PathBuf,
thread::sleep,
time::{Duration, Instant},
};
use svg::{
node::{
element::{path::Data, Element, Group, Path},
Attributes,
},
Document, Node,
};
pub enum ArtAction {
Pause(String, f64),
ChatMessage(String),
}
pub enum ArtIncrement {
SVG(Vec<Group>),
Continue,
End,
}
pub trait LivedrawArt {
fn draw_increment(&mut self, input: &Value, index: usize) -> ArtIncrement;
fn get_dimension(&self) -> (f64, f64);
fn estimate_total_increments(&self) -> usize;
fn actions_before_increment(&self, _index: usize) -> Vec<ArtAction> {
vec![]
}
fn get_predictive_max_next_increments(&self) -> Option<usize> {
None
}
fn delay_between_increments(&self) -> Duration {
Duration::from_secs(1)
}
}
pub trait LivedrawArtSimulation {
fn simulate_input(&mut self, index: usize) -> Value;
}
pub fn livedraw_start_simulation<
T: LivedrawArt + LivedrawArtSimulation + Clone,
>(
art: &mut T,
) {
std::fs::create_dir_all(files_folder().join("files")).unwrap();
let (width, height) = art.get_dimension();
let mut doc = svg_document(width, height);
let mut i = 0usize;
loop {
let input = art.simulate_input(i);
match art.draw_increment(&input, i) {
ArtIncrement::End => {
break;
}
ArtIncrement::Continue => {
i += 1;
continue;
}
ArtIncrement::SVG(parts) => {
for part in parts {
doc = doc.add(part);
}
i += 1;
}
}
}
let file = files_folder().join("all.svg");
svg::save(file, &doc).unwrap();
}
pub fn livedraw_start<T: LivedrawArt + Clone>(art: &mut T) {
let predictive_svg_freq = Duration::from_millis(500);
let folder = files_folder();
let increment_path = folder.join("increment.svg");
let increment_finished_path = folder.join("increment.finished.svg");
let predictive_path = folder.join("predictive.svg");
let all_path = folder.join("all.svg");
std::fs::remove_file(increment_path.clone()).unwrap_or(());
std::fs::remove_file(increment_finished_path.clone()).unwrap_or(());
std::fs::remove_file(predictive_path.clone()).unwrap_or(());
std::fs::remove_file(all_path.clone()).unwrap_or(());
std::fs::create_dir_all(folder).unwrap();
let (width, height) = art.get_dimension();
plot_update(PlotUpdateAction::PlotArtStart());
let delay = art.delay_between_increments();
let mut i: usize = 0;
let mut all_doc = svg_document(width, height);
let mut last_input = get_input();
generate_predictive(art, &last_input);
loop {
let before = Instant::now();
let mut doc = svg_document(width, height);
for action in art.actions_before_increment(i) {
match action {
ArtAction::Pause(text, duration) => {
plot_update(PlotUpdateAction::PlotArtCountdownPause(text, duration));
let end = Instant::now() + Duration::from_secs_f64(duration);
loop {
if Instant::now() > end {
break;
}
let input = get_input();
if last_input != input {
last_input = input.clone();
generate_predictive(art, &input);
}
sleep(predictive_svg_freq);
}
}
ArtAction::ChatMessage(message) => {
plot_update(PlotUpdateAction::PlotArtChatMessage(message));
}
}
}
let total = art.estimate_total_increments();
plot_update(PlotUpdateAction::PlotIncrPrepare(i, total));
let input = get_input();
if last_input != input {
last_input = input.clone();
generate_predictive(art, &input);
}
match art.draw_increment(&input, i) {
ArtIncrement::End => {
break;
}
ArtIncrement::Continue => {
i += 1;
continue;
}
ArtIncrement::SVG(parts) => {
for part in parts {
doc = doc.add(part.clone());
all_doc = all_doc.add(part.clone());
}
doc = doc.add(
svg::node::element::Group::new()
.set("inkscape:groupmode", "layer")
.set("id", "force_pause")
.set("inkscape:label", "!"),
);
if let Some(plotdata_attributes) = plot_read_previous_plot_data() {
doc = doc.add(plot_make_new_plotdata(plotdata_attributes));
}
plot_update(PlotUpdateAction::PlotIncrStart(i));
svg::save(increment_path.clone(), &doc).unwrap();
svg::save(all_path.clone(), &all_doc).unwrap();
loop {
let exists = increment_path.as_path().is_file();
if !exists {
plot_update(PlotUpdateAction::PlotIncrEnd(i));
break;
}
let input = get_input();
if last_input != input {
last_input = input.clone();
generate_predictive(art, &input);
}
sleep(predictive_svg_freq);
}
loop {
let after = Instant::now();
let ellapsed = after - before;
if ellapsed < delay {
let input = get_input();
if last_input != input {
last_input = input.clone();
generate_predictive(art, &input);
}
sleep(predictive_svg_freq);
} else {
break;
}
}
i += 1;
}
}
}
plot_cleanup(width, height);
plot_update(PlotUpdateAction::PlotArtStop());
}
fn plot_cleanup(width: f64, height: f64) {
if let Some(attrs) = plot_read_previous_plot_data() {
let mut doc = svg_document(width, height);
doc = doc.add(plot_make_new_plotdata(attrs));
let file = files_folder().join("increment.svg");
svg::save(file, &doc).unwrap();
}
}
fn plot_make_new_plotdata(attributes: Attributes) -> Element {
let mut attributes = attributes.clone();
attributes.insert("layer".to_string(), "-1".to_string().into()); attributes.insert("pause_dist".to_string(), "0".to_string().into()); attributes.insert("pause_ref".to_string(), "0".to_string().into());
let mut new_element = Element::new("plotdata");
for attr in attributes {
new_element.assign(attr.0, attr.1);
}
new_element
}
fn plot_read_previous_plot_data() -> Option<Attributes> {
let mut svg_str = String::new();
let file = files_folder().join("increment.finished.svg");
svg::open(file.clone(), &mut svg_str).ok().and_then(|doc| {
for event in doc {
match event {
svg::parser::Event::Tag("plotdata", _, attributes) => {
std::fs::remove_file(file).unwrap();
return Some(attributes);
}
_ => {}
}
}
None
})
}
fn generate_svg_all<T: LivedrawArt + Clone>(
art: &T,
input: &Value,
max_iterations: Option<usize>,
) -> Document {
let mut copy = art.clone();
let (width, height) = copy.get_dimension();
let mut doc = svg_document(width, height);
let mut i = 0;
loop {
if let Some(max) = max_iterations {
if i > max {
break;
}
}
match copy.draw_increment(&input, i) {
ArtIncrement::End => {
break;
}
ArtIncrement::Continue => {
i += 1;
continue;
}
ArtIncrement::SVG(parts) => {
for part in parts {
doc = doc.add(part);
}
i += 1;
}
}
}
doc
}
fn generate_predictive<T: LivedrawArt + Clone>(art: &T, input: &Value) {
let predictive_doc =
generate_svg_all(art, input, art.get_predictive_max_next_increments());
let file = files_folder().join("predictive.svg");
svg::save(file, &predictive_doc).unwrap();
plot_update(PlotUpdateAction::PlotPredictiveWritten());
}
fn retry_function<F, T, E>(mut f: F) -> Result<T, E>
where
E: Debug,
F: FnMut() -> Result<T, E>,
{
let mut result = f();
while result.is_err() {
match result {
Err(e) => {
println!("retry failed: {:#?}", e)
}
Ok(_) => {}
};
sleep(Duration::from_millis(500));
result = f();
}
result
}
fn get_input() -> Value {
let value: Value = retry_function(|| {
let mut response = isahc::get("http://localhost:4628/state/inputs")
.map_err(|e| e.to_string())?;
if response.status().is_success() {
let text = response.text().map_err(|e| e.to_string())?;
serde_json::from_str(text.as_str()).map_err(|e| e.to_string())
} else {
Err(String::from("non successful status code"))
}
})
.unwrap();
value
}
enum PlotUpdateAction {
PlotArtCountdownPause(String, f64),
PlotArtChatMessage(String),
PlotArtStart(),
PlotArtStop(),
PlotIncrPrepare(usize, usize),
PlotIncrStart(usize),
PlotIncrEnd(usize),
PlotPredictiveWritten(),
}
fn plot_update(action: PlotUpdateAction) {
let body = match action {
PlotUpdateAction::PlotArtCountdownPause(text, duration) => json!({
"type": "art-countdown-pause",
"text": text,
"duration": duration
}),
PlotUpdateAction::PlotArtChatMessage(text) => json!({
"type": "chat-message",
"text": text
}),
PlotUpdateAction::PlotArtStart() => json!({
"type": "art-start"
}),
PlotUpdateAction::PlotArtStop() => json!({
"type": "art-stop"
}),
PlotUpdateAction::PlotIncrPrepare(index, total) => json!({
"type": "incr-prepare",
"index": index,
"total": total,
}),
PlotUpdateAction::PlotIncrStart(index) => json!({
"type": "incr-start",
"index": index,
}),
PlotUpdateAction::PlotIncrEnd(index) => json!({
"type": "incr-end",
"index": index,
}),
PlotUpdateAction::PlotPredictiveWritten() => json!({
"type": "predictive"
}),
};
println!("{}", body);
retry_function(|| {
isahc::Request::post("http://localhost:4628/plot-update")
.header("Content-Type", "application/json")
.body(body.to_string())
.unwrap()
.send()
})
.unwrap();
}
fn files_folder() -> PathBuf {
let mut path = dirs::home_dir().unwrap();
path.push(".livedraw");
path.push("files");
path
}
pub fn render_route(data: Data, route: &Vec<(f64, f64)>) -> Data {
let mut first = true;
let mut d = data;
for &p in route {
if first {
first = false;
d = d.move_to(p);
} else {
d = d.line_to(p);
}
}
return d;
}
pub fn svg_layer(id: &str) -> Group {
return Group::new()
.set("inkscape:groupmode", "layer")
.set("inkscape:label", id);
}
pub fn svg_document(width: f64, height: f64) -> Document {
Document::new()
.set(
"xmlns:inkscape",
"http://www.inkscape.org/namespaces/inkscape",
)
.set("viewBox", (0, 0, width, height))
.set("width", format!("{}mm", width))
.set("height", format!("{}mm", height))
}
pub fn svg_base_path(color: &str, stroke_width: f64, data: Data) -> Path {
Path::new()
.set("fill", "none")
.set("stroke", color)
.set("stroke-width", stroke_width)
.set("d", data)
.set("style", "mix-blend-mode: multiply;")
}