Skip to content

Commit 8be2327

Browse files
Merge pull request #1674 from CapSoftware/rendering-engine-tweaks
Improved rendering engine, recording resilience, and analytics trackingRendering engine tweaks
2 parents 3766eac + a440a32 commit 8be2327

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3918
-1077
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dist-ssr
1616
.env.development
1717
.env.test
1818
target
19+
target-agent
1920
.cursorrules
2021
.github/hooks
2122

@@ -68,4 +69,5 @@ scripts/releases-backfill-data.txt
6869
.opencode/
6970
analysis/
7071
analysis/plans/
71-
.ralphy
72+
.ralphy
73+
tmp/

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ All Rust code must respect these workspace-level lints defined in `Cargo.toml`:
3131
**Clippy lints (all denied):**
3232
- `dbg_macro` — Never use `dbg!()` in code; use proper logging instead.
3333
- `let_underscore_future` — Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them.
34-
- `unchecked_duration_subtraction` — Use `saturating_sub` instead of `-` for `Duration` to avoid panics.
34+
- `unchecked_time_subtraction` — Use `saturating_sub` instead of `-` for `Duration` to avoid panics.
3535
- `collapsible_if` — Merge nested `if` statements: use `if a && b { }` instead of `if a { if b { } }`.
3636
- `clone_on_copy` — Don't call `.clone()` on `Copy` types; just copy them directly.
3737
- `redundant_closure` — Use function references directly: `iter.map(foo)` instead of `iter.map(|x| foo(x))`.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ All Rust code must respect these workspace-level lints defined in `Cargo.toml`.
380380
**Clippy lints (all denied — code MUST NOT contain these patterns):**
381381
- `dbg_macro` — Never use `dbg!()` in code; use proper logging (`tracing::debug!`, etc.) instead.
382382
- `let_underscore_future` — Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them.
383-
- `unchecked_duration_subtraction` — Use `duration.saturating_sub(other)` instead of `duration - other` to avoid panics on underflow.
383+
- `unchecked_time_subtraction` — Use `duration.saturating_sub(other)` instead of `duration - other` to avoid panics on underflow.
384384
- `collapsible_if` — Merge nested `if` statements: write `if a && b { }` instead of `if a { if b { } }`.
385385
- `clone_on_copy` — Don't call `.clone()` on `Copy` types (integers, bools, etc.); just copy them directly.
386386
- `redundant_closure` — Use function references directly: `iter.map(foo)` instead of `iter.map(|x| foo(x))`.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ unused_must_use = "deny"
8383
[workspace.lints.clippy]
8484
dbg_macro = "deny"
8585
let_underscore_future = "deny"
86-
unchecked_duration_subtraction = "deny"
86+
unchecked_time_subtraction = "deny"
8787
collapsible_if = "deny"
8888
clone_on_copy = "deny"
8989
redundant_closure = "deny"

apps/desktop/src-tauri/src/export.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,14 @@ pub async fn generate_export_preview(
336336
let zoom_focus_interpolator = ZoomFocusInterpolator::new(
337337
&render_segment.cursor,
338338
cursor_smoothing,
339+
project_config.cursor.click_spring_config(),
339340
project_config.screen_movement_spring,
340341
total_duration,
342+
project_config
343+
.timeline
344+
.as_ref()
345+
.map(|t| t.zoom_segments.as_slice())
346+
.unwrap_or(&[]),
341347
);
342348

343349
let uniforms = ProjectUniforms::new(
@@ -482,8 +488,14 @@ pub async fn generate_export_preview_fast(
482488
let zoom_focus_interpolator = ZoomFocusInterpolator::new(
483489
&segment_media.cursor,
484490
cursor_smoothing,
491+
project_config.cursor.click_spring_config(),
485492
project_config.screen_movement_spring,
486493
total_duration,
494+
project_config
495+
.timeline
496+
.as_ref()
497+
.map(|t| t.zoom_segments.as_slice())
498+
.unwrap_or(&[]),
487499
);
488500

489501
let uniforms = ProjectUniforms::new(

apps/desktop/src-tauri/src/posthog.rs

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use std::{
22
sync::{OnceLock, PoisonError, RwLock},
33
time::Duration,
44
};
5+
use tauri::AppHandle;
56
use tracing::error;
67

8+
use crate::auth::AuthStore;
9+
710
#[derive(Debug)]
811
pub enum PostHogEvent {
912
MultipartUploadComplete {
@@ -22,36 +25,42 @@ pub enum PostHogEvent {
2225
},
2326
}
2427

25-
impl From<PostHogEvent> for posthog_rs::Event {
26-
fn from(event: PostHogEvent) -> Self {
27-
match event {
28-
PostHogEvent::MultipartUploadComplete {
29-
duration,
30-
length,
31-
size,
32-
} => {
33-
let mut e = posthog_rs::Event::new_anon("multipart_upload_complete");
34-
e.insert_prop("duration", duration.as_secs())
35-
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
36-
.ok();
37-
e.insert_prop("length", length.as_secs())
38-
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
39-
.ok();
40-
e.insert_prop("size", size)
41-
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
42-
.ok();
43-
e
44-
}
45-
PostHogEvent::MultipartUploadFailed { duration, error } => {
46-
let mut e = posthog_rs::Event::new_anon("multipart_upload_failed");
47-
e.insert_prop("duration", duration.as_secs())
48-
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
49-
.ok();
50-
e.insert_prop("error", error)
51-
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
52-
.ok();
53-
e
54-
}
28+
fn posthog_event(event: PostHogEvent, distinct_id: Option<&str>) -> posthog_rs::Event {
29+
match event {
30+
PostHogEvent::MultipartUploadComplete {
31+
duration,
32+
length,
33+
size,
34+
} => {
35+
let mut e = match distinct_id {
36+
Some(distinct_id) => {
37+
posthog_rs::Event::new("multipart_upload_complete", distinct_id)
38+
}
39+
None => posthog_rs::Event::new_anon("multipart_upload_complete"),
40+
};
41+
e.insert_prop("duration", duration.as_secs())
42+
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
43+
.ok();
44+
e.insert_prop("length", length.as_secs())
45+
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
46+
.ok();
47+
e.insert_prop("size", size)
48+
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
49+
.ok();
50+
e
51+
}
52+
PostHogEvent::MultipartUploadFailed { duration, error } => {
53+
let mut e = match distinct_id {
54+
Some(distinct_id) => posthog_rs::Event::new("multipart_upload_failed", distinct_id),
55+
None => posthog_rs::Event::new_anon("multipart_upload_failed"),
56+
};
57+
e.insert_prop("duration", duration.as_secs())
58+
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
59+
.ok();
60+
e.insert_prop("error", error)
61+
.map_err(|err| error!("Error adding PostHog property: {err:?}"))
62+
.ok();
63+
e
5564
}
5665
}
5766
}
@@ -76,10 +85,14 @@ pub fn set_server_url(url: &str) {
7685

7786
static API_SERVER_IS_CAP_CLOUD: OnceLock<RwLock<Option<bool>>> = OnceLock::new();
7887

79-
pub fn async_capture_event(event: PostHogEvent) {
88+
pub fn async_capture_event(app: &AppHandle, event: PostHogEvent) {
8089
if option_env!("VITE_POSTHOG_KEY").is_some() {
90+
let distinct_id = AuthStore::get(app)
91+
.ok()
92+
.flatten()
93+
.and_then(|auth| auth.user_id);
8194
tokio::spawn(async move {
82-
let mut e: posthog_rs::Event = event.into();
95+
let mut e = posthog_event(event, distinct_id.as_deref());
8396

8497
e.insert_prop("cap_version", env!("CARGO_PKG_VERSION"))
8598
.map_err(|err| error!("Error adding PostHog property: {err:?}"))

apps/desktop/src-tauri/src/screenshot_editor.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,14 @@ impl ScreenshotEditorInstances {
356356
let zoom_focus_interpolator = ZoomFocusInterpolator::new(
357357
&cursor_events,
358358
None,
359+
current_config.cursor.click_spring_config(),
359360
current_config.screen_movement_spring,
360361
0.0,
362+
current_config
363+
.timeline
364+
.as_ref()
365+
.map(|t| t.zoom_segments.as_slice())
366+
.unwrap_or(&[]),
361367
);
362368

363369
let uniforms = ProjectUniforms::new(

apps/desktop/src-tauri/src/upload.rs

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -146,22 +146,25 @@ pub async fn upload_video(
146146

147147
emit_upload_complete(app, &video_id);
148148

149-
async_capture_event(match &video_result {
150-
Ok(meta) => PostHogEvent::MultipartUploadComplete {
151-
duration: start.elapsed(),
152-
length: meta
153-
.as_ref()
154-
.map(|v| Duration::from_secs(v.duration_in_secs as u64))
155-
.unwrap_or_default(),
156-
size: std::fs::metadata(file_path)
157-
.map(|m| ((m.len() as f64) / 1_000_000.0) as u64)
158-
.unwrap_or_default(),
159-
},
160-
Err(err) => PostHogEvent::MultipartUploadFailed {
161-
duration: start.elapsed(),
162-
error: err.to_string(),
149+
async_capture_event(
150+
app,
151+
match &video_result {
152+
Ok(meta) => PostHogEvent::MultipartUploadComplete {
153+
duration: start.elapsed(),
154+
length: meta
155+
.as_ref()
156+
.map(|v| Duration::from_secs(v.duration_in_secs as u64))
157+
.unwrap_or_default(),
158+
size: std::fs::metadata(file_path)
159+
.map(|m| ((m.len() as f64) / 1_000_000.0) as u64)
160+
.unwrap_or_default(),
161+
},
162+
Err(err) => PostHogEvent::MultipartUploadFailed {
163+
duration: start.elapsed(),
164+
error: err.to_string(),
165+
},
163166
},
164-
});
167+
);
165168

166169
let _ = (video_result?, thumbnail_result?);
167170

@@ -399,29 +402,32 @@ impl InstantMultipartUpload {
399402
handle: spawn_actor(async move {
400403
let start = Instant::now();
401404
let result = Self::run(
402-
app,
405+
app.clone(),
403406
file_path.clone(),
404407
pre_created_video,
405408
recording_dir,
406409
realtime_upload_done,
407410
)
408411
.await;
409-
async_capture_event(match &result {
410-
Ok(meta) => PostHogEvent::MultipartUploadComplete {
411-
duration: start.elapsed(),
412-
length: meta
413-
.as_ref()
414-
.map(|v| Duration::from_secs(v.duration_in_secs as u64))
415-
.unwrap_or_default(),
416-
size: std::fs::metadata(file_path)
417-
.map(|m| ((m.len() as f64) / 1_000_000.0) as u64)
418-
.unwrap_or_default(),
419-
},
420-
Err(err) => PostHogEvent::MultipartUploadFailed {
421-
duration: start.elapsed(),
422-
error: err.to_string(),
412+
async_capture_event(
413+
&app,
414+
match &result {
415+
Ok(meta) => PostHogEvent::MultipartUploadComplete {
416+
duration: start.elapsed(),
417+
length: meta
418+
.as_ref()
419+
.map(|v| Duration::from_secs(v.duration_in_secs as u64))
420+
.unwrap_or_default(),
421+
size: std::fs::metadata(file_path)
422+
.map(|m| ((m.len() as f64) / 1_000_000.0) as u64)
423+
.unwrap_or_default(),
424+
},
425+
Err(err) => PostHogEvent::MultipartUploadFailed {
426+
duration: start.elapsed(),
427+
error: err.to_string(),
428+
},
423429
},
424-
});
430+
);
425431

426432
result.map(|_| ())
427433
}),

apps/desktop/src/routes/editor/ConfigSidebar.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ type CursorPresetValues = {
224224
friction: number;
225225
};
226226

227-
const DEFAULT_CURSOR_MOTION_BLUR = 0.5;
227+
const DEFAULT_CURSOR_MOTION_BLUR = 1.0;
228228

229229
const CURSOR_TYPE_OPTIONS = [
230230
{
@@ -619,6 +619,16 @@ export function ConfigSidebar() {
619619
step={1}
620620
/>
621621
</Field>
622+
<Field name="Tilt" icon={<IconLucideRotate3d class="size-4" />}>
623+
<Slider
624+
value={[project.cursor.rotationAmount ?? 0.15]}
625+
onChange={(v) => setProject("cursor", "rotationAmount", v[0])}
626+
minValue={0}
627+
maxValue={1}
628+
step={0.01}
629+
formatTooltip={(value) => `${Math.round(value * 100)}%`}
630+
/>
631+
</Field>
622632
<Field
623633
name="Hide When Idle"
624634
icon={<IconLucideTimer class="size-4" />}

apps/desktop/src/utils/frame-worker.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,6 @@ function drainAndRenderLatestSharedWebGPU(maxDrain: number): boolean {
417417
if (renderMode !== "webgpu" || !webgpuRenderer) return false;
418418

419419
let latest: { bytes: Uint8Array; release: () => void } | null = null;
420-
let drained = 0;
421420

422421
for (let i = 0; i < maxDrain; i += 1) {
423422
const borrowed = consumer.borrow(0);
@@ -427,7 +426,6 @@ function drainAndRenderLatestSharedWebGPU(maxDrain: number): boolean {
427426
latest.release();
428427
}
429428
latest = { bytes: borrowed.data, release: borrowed.release };
430-
drained += 1;
431429
}
432430

433431
if (!latest) return false;

0 commit comments

Comments
 (0)