Eliminate imperative redraws.

This commit removes Platform::Window::Redraw function, and rewrites
its uses to run on timer events. Most UI toolkits have obscure issues
with recursive event handling loops, and Emscripten is purely event-
driven and cannot handle imperative redraws at all.

As a part of this change, the Platform::Timer::WindUp function
is split into three to make the interpretation of its argument
less magical. The new functions are RunAfter (a regular timeout,
setTimeout in browser terms), RunAfterNextFrame (an animation
request, requestAnimationFrame in browser terms), and
RunAfterProcessingEvents (a request to run something after all
events for the current frame are processed, used for coalescing
expensive operations in face of input event queues).

This commit changes two uses of Redraw(): the AnimateOnto() and
ScreenStepDimGo() functions. The latter was actually broken in that
on small sketches, it would run very quickly and not animate
the dimension change at all; this has been fixed.

While we're at it, get rid of unused Platform::Window::NativePtr
function as well.
This commit is contained in:
whitequark 2018-07-18 23:11:49 +00:00
parent 738ac02cbf
commit a738e3f82e
11 changed files with 106 additions and 103 deletions

View File

@ -423,6 +423,9 @@ void GraphicsWindow::AnimateOntoWorkplane() {
Quaternion quatf = w->Normal()->NormalGetNum(); Quaternion quatf = w->Normal()->NormalGetNum();
Vector offsetf = (SK.GetEntity(w->point[0])->PointGetNum()).ScaledBy(-1); Vector offsetf = (SK.GetEntity(w->point[0])->PointGetNum()).ScaledBy(-1);
// If the view screen is open, then we need to refresh it.
SS.ScheduleShowTW();
AnimateOnto(quatf, offsetf); AnimateOnto(quatf, offsetf);
} }
@ -441,34 +444,37 @@ void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) {
double mo = (offset0.Minus(offsetf)).Magnitude()*scale; double mo = (offset0.Minus(offsetf)).Magnitude()*scale;
// Animate transition, unless it's a tiny move. // Animate transition, unless it's a tiny move.
int64_t t0 = GetMilliseconds();
int32_t dt = (mp < 0.01 && mo < 10) ? (-20) : int32_t dt = (mp < 0.01 && mo < 10) ? (-20) :
(int32_t)(100 + 1000*mp + 0.4*mo); (int32_t)(100 + 1000*mp + 0.4*mo);
// Don't ever animate for longer than 2000 ms; we can get absurdly // Don't ever animate for longer than 2000 ms; we can get absurdly
// long translations (as measured in pixels) if the user zooms out, moves, // long translations (as measured in pixels) if the user zooms out, moves,
// and then zooms in again. // and then zooms in again.
if(dt > 2000) dt = 2000; if(dt > 2000) dt = 2000;
int64_t tn, t0 = GetMilliseconds();
double s = 0;
Quaternion dq = quatf.Times(quat0.Inverse()); Quaternion dq = quatf.Times(quat0.Inverse());
do {
if(!animateTimer) {
animateTimer = Platform::CreateTimer();
}
animateTimer->onTimeout = [=] {
int64_t tn = GetMilliseconds();
if((tn - t0) < dt) {
animateTimer->RunAfterNextFrame();
double s = (tn - t0)/((double)dt);
offset = (offset0.ScaledBy(1 - s)).Plus(offsetf.ScaledBy(s)); offset = (offset0.ScaledBy(1 - s)).Plus(offsetf.ScaledBy(s));
Quaternion quat = (dq.ToThe(s)).Times(quat0); Quaternion quat = (dq.ToThe(s)).Times(quat0).WithMagnitude(1);
quat = quat.WithMagnitude(1);
projRight = quat.RotationU(); projRight = quat.RotationU();
projUp = quat.RotationV(); projUp = quat.RotationV();
window->Redraw(); } else {
tn = GetMilliseconds();
s = (tn - t0)/((double)dt);
} while((tn - t0) < dt);
projRight = quatf.RotationU(); projRight = quatf.RotationU();
projUp = quatf.RotationV(); projUp = quatf.RotationV();
offset = offsetf; offset = offsetf;
Invalidate(); }
// If the view screen is open, then we need to refresh it. window->Invalidate();
SS.ScheduleShowTW(); };
animateTimer->RunAfterNextFrame();
} }
void GraphicsWindow::HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin, void GraphicsWindow::HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin,
@ -695,7 +701,6 @@ void GraphicsWindow::MenuView(Command id) {
case Command::ONTO_WORKPLANE: case Command::ONTO_WORKPLANE:
if(SS.GW.LockedInWorkplane()) { if(SS.GW.LockedInWorkplane()) {
SS.GW.AnimateOntoWorkplane(); SS.GW.AnimateOntoWorkplane();
SS.ScheduleShowTW();
break; break;
} // if not in 2d mode fall through and use ORTHO logic } // if not in 2d mode fall through and use ORTHO logic
case Command::NEAREST_ORTHO: case Command::NEAREST_ORTHO:
@ -760,8 +765,8 @@ void GraphicsWindow::MenuView(Command id) {
// Offset is the selected point, quaternion is same as before // Offset is the selected point, quaternion is same as before
Vector pt = SK.GetEntity(SS.GW.gs.point[0])->PointGetNum(); Vector pt = SK.GetEntity(SS.GW.gs.point[0])->PointGetNum();
quat0 = Quaternion::From(SS.GW.projRight, SS.GW.projUp); quat0 = Quaternion::From(SS.GW.projRight, SS.GW.projUp);
SS.GW.AnimateOnto(quat0, pt.ScaledBy(-1));
SS.GW.ClearSelection(); SS.GW.ClearSelection();
SS.GW.AnimateOnto(quat0, pt.ScaledBy(-1));
} else { } else {
Error(_("Select a point; this point will become the center " Error(_("Select a point; this point will become the center "
"of the view on screen.")); "of the view on screen."));
@ -1175,9 +1180,8 @@ void GraphicsWindow::MenuRequest(Command id) {
break; break;
} }
// Align the view with the selected workplane // Align the view with the selected workplane
SS.GW.AnimateOntoWorkplane();
SS.GW.ClearSuper(); SS.GW.ClearSuper();
SS.ScheduleShowTW(); SS.GW.AnimateOntoWorkplane();
break; break;
} }
case Command::FREE_IN_3D: case Command::FREE_IN_3D:

View File

@ -291,9 +291,8 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
gg->activeWorkplane = gg->h.entity(0); gg->activeWorkplane = gg->h.entity(0);
} }
gg->Activate(); gg->Activate();
SS.GW.AnimateOntoWorkplane();
TextWindow::ScreenSelectGroup(0, gg->h.v); TextWindow::ScreenSelectGroup(0, gg->h.v);
SS.ScheduleShowTW(); SS.GW.AnimateOntoWorkplane();
} }
void Group::TransformImportedBy(Vector t, Quaternion q) { void Group::TransformImportedBy(Vector t, Quaternion q) {

View File

@ -135,7 +135,9 @@ public:
virtual ~Timer() {} virtual ~Timer() {}
virtual void WindUp(unsigned milliseconds) = 0; virtual void RunAfter(unsigned milliseconds) = 0;
virtual void RunAfterNextFrame() { RunAfter(1); }
virtual void RunAfterProcessingEvents() { RunAfter(0); }
}; };
typedef std::unique_ptr<Timer> TimerRef; typedef std::unique_ptr<Timer> TimerRef;
@ -261,9 +263,6 @@ public:
virtual void SetScrollbarPosition(double pos) = 0; virtual void SetScrollbarPosition(double pos) = 0;
virtual void Invalidate() = 0; virtual void Invalidate() = 0;
virtual void Redraw() = 0;
virtual void *NativePtr() = 0;
}; };
typedef std::shared_ptr<Window> WindowRef; typedef std::shared_ptr<Window> WindowRef;

View File

@ -202,7 +202,7 @@ class TimerImplGtk : public Timer {
public: public:
sigc::connection _connection; sigc::connection _connection;
void WindUp(unsigned milliseconds) override { void RunAfter(unsigned milliseconds) override {
if(!_connection.empty()) { if(!_connection.empty()) {
_connection.disconnect(); _connection.disconnect();
} }
@ -982,15 +982,6 @@ public:
void Invalidate() override { void Invalidate() override {
gtkWindow.get_gl_widget().queue_render(); gtkWindow.get_gl_widget().queue_render();
} }
void Redraw() override {
Invalidate();
Gtk::Main::iteration(/*blocking=*/false);
}
void *NativePtr() override {
return &gtkWindow;
}
}; };
WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {

View File

@ -165,7 +165,7 @@ public:
TimerImplCocoa() : timer(NULL) {} TimerImplCocoa() : timer(NULL) {}
void WindUp(unsigned milliseconds) override { void RunAfter(unsigned milliseconds) override {
SSFunction *callback = [[SSFunction alloc] initWithFunction:&this->onTimeout]; SSFunction *callback = [[SSFunction alloc] initWithFunction:&this->onTimeout];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[callback methodSignatureForSelector:@selector(run)]]; [callback methodSignatureForSelector:@selector(run)]];
@ -984,15 +984,6 @@ public:
void Invalidate() override { void Invalidate() override {
ssView.needsDisplay = YES; ssView.needsDisplay = YES;
} }
void Redraw() override {
Invalidate();
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
}
void *NativePtr() override {
return (__bridge void *)ssView;
}
}; };
WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {

View File

@ -85,7 +85,7 @@ SettingsRef GetSettings() {
class TimerImplDummy : public Timer { class TimerImplDummy : public Timer {
public: public:
void WindUp(unsigned milliseconds) override {} void RunAfter(unsigned milliseconds) override {}
}; };
TimerRef CreateTimer() { TimerRef CreateTimer() {

View File

@ -244,7 +244,7 @@ public:
} }
} }
void WindUp(unsigned milliseconds) override { void RunAfter(unsigned milliseconds) override {
// FIXME(platform/gui): use SetCoalescableTimer when it's available (8+) // FIXME(platform/gui): use SetCoalescableTimer when it's available (8+)
sscheck(SetTimer(WindowHandle(), (UINT_PTR)this, sscheck(SetTimer(WindowHandle(), (UINT_PTR)this,
milliseconds, &TimerImplWin32::TimerFunc)); milliseconds, &TimerImplWin32::TimerFunc));
@ -1310,15 +1310,6 @@ public:
void Invalidate() override { void Invalidate() override {
sscheck(InvalidateRect(hWindow, NULL, /*bErase=*/FALSE)); sscheck(InvalidateRect(hWindow, NULL, /*bErase=*/FALSE));
} }
void Redraw() override {
Invalidate();
sscheck(UpdateWindow(hWindow));
}
void *NativePtr() override {
return hWindow;
}
}; };
WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {

View File

@ -111,15 +111,15 @@ void SolveSpaceUI::Init() {
SetLocale(locale); SetLocale(locale);
} }
timerGenerateAll = Platform::CreateTimer(); generateAllTimer = Platform::CreateTimer();
timerGenerateAll->onTimeout = std::bind(&SolveSpaceUI::GenerateAll, &SS, Generate::DIRTY, generateAllTimer->onTimeout = std::bind(&SolveSpaceUI::GenerateAll, &SS, Generate::DIRTY,
/*andFindFree=*/false, /*genForBBox=*/false); /*andFindFree=*/false, /*genForBBox=*/false);
timerShowTW = Platform::CreateTimer(); showTWTimer = Platform::CreateTimer();
timerShowTW->onTimeout = std::bind(&TextWindow::Show, &TW); showTWTimer->onTimeout = std::bind(&TextWindow::Show, &TW);
timerAutosave = Platform::CreateTimer(); autosaveTimer = Platform::CreateTimer();
timerAutosave->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS); autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS);
// The default styles (colors, line widths, etc.) are also stored in the // The default styles (colors, line widths, etc.) are also stored in the
// configuration file, but we will automatically load those as we need // configuration file, but we will automatically load those as we need
@ -275,15 +275,15 @@ void SolveSpaceUI::Exit() {
} }
void SolveSpaceUI::ScheduleGenerateAll() { void SolveSpaceUI::ScheduleGenerateAll() {
timerGenerateAll->WindUp(0); generateAllTimer->RunAfterProcessingEvents();
} }
void SolveSpaceUI::ScheduleShowTW() { void SolveSpaceUI::ScheduleShowTW() {
timerShowTW->WindUp(0); showTWTimer->RunAfterProcessingEvents();
} }
void SolveSpaceUI::ScheduleAutosave() { void SolveSpaceUI::ScheduleAutosave() {
timerAutosave->WindUp(autosaveInterval * 60 * 1000); autosaveTimer->RunAfter(autosaveInterval * 60 * 1000);
} }
double SolveSpaceUI::MmPerUnit() { double SolveSpaceUI::MmPerUnit() {
@ -646,9 +646,9 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
if(gs.constraints == 1 && gs.n == 0) { if(gs.constraints == 1 && gs.n == 0) {
Constraint *c = SK.GetConstraint(gs.constraint[0]); Constraint *c = SK.GetConstraint(gs.constraint[0]);
if(c->HasLabel() && !c->reference) { if(c->HasLabel() && !c->reference) {
SS.TW.shown.dimFinish = c->valA; SS.TW.stepDim.finish = c->valA;
SS.TW.shown.dimSteps = 10; SS.TW.stepDim.steps = 10;
SS.TW.shown.dimIsDistance = SS.TW.stepDim.isDistance =
(c->type != Constraint::Type::ANGLE) && (c->type != Constraint::Type::ANGLE) &&
(c->type != Constraint::Type::LENGTH_RATIO) && (c->type != Constraint::Type::LENGTH_RATIO) &&
(c->type != Constraint::Type::LENGTH_DIFFERENCE); (c->type != Constraint::Type::LENGTH_DIFFERENCE);

View File

@ -789,9 +789,9 @@ public:
// the sketch! // the sketch!
bool allConsistent; bool allConsistent;
Platform::TimerRef timerShowTW; Platform::TimerRef showTWTimer;
Platform::TimerRef timerGenerateAll; Platform::TimerRef generateAllTimer;
Platform::TimerRef timerAutosave; Platform::TimerRef autosaveTimer;
void ScheduleShowTW(); void ScheduleShowTW();
void ScheduleGenerateAll(); void ScheduleGenerateAll();
void ScheduleAutosave(); void ScheduleAutosave();

View File

@ -550,38 +550,57 @@ void TextWindow::ShowGroupSolveInfo() {
void TextWindow::ScreenStepDimFinish(int link, uint32_t v) { void TextWindow::ScreenStepDimFinish(int link, uint32_t v) {
SS.TW.edit.meaning = Edit::STEP_DIM_FINISH; SS.TW.edit.meaning = Edit::STEP_DIM_FINISH;
std::string edit_value; std::string edit_value;
if(SS.TW.shown.dimIsDistance) { if(SS.TW.stepDim.isDistance) {
edit_value = SS.MmToString(SS.TW.shown.dimFinish); edit_value = SS.MmToString(SS.TW.stepDim.finish);
} else { } else {
edit_value = ssprintf("%.3f", SS.TW.shown.dimFinish); edit_value = ssprintf("%.3f", SS.TW.stepDim.finish);
} }
SS.TW.ShowEditControl(12, edit_value); SS.TW.ShowEditControl(12, edit_value);
} }
void TextWindow::ScreenStepDimSteps(int link, uint32_t v) { void TextWindow::ScreenStepDimSteps(int link, uint32_t v) {
SS.TW.edit.meaning = Edit::STEP_DIM_STEPS; SS.TW.edit.meaning = Edit::STEP_DIM_STEPS;
SS.TW.ShowEditControl(12, ssprintf("%d", SS.TW.shown.dimSteps)); SS.TW.ShowEditControl(12, ssprintf("%d", SS.TW.stepDim.steps));
} }
void TextWindow::ScreenStepDimGo(int link, uint32_t v) { void TextWindow::ScreenStepDimGo(int link, uint32_t v) {
hConstraint hc = SS.TW.shown.constraint; hConstraint hc = SS.TW.shown.constraint;
Constraint *c = SK.constraint.FindByIdNoOops(hc); Constraint *c = SK.constraint.FindByIdNoOops(hc);
if(c) { if(c) {
SS.UndoRemember(); SS.UndoRemember();
double start = c->valA, finish = SS.TW.shown.dimFinish;
int i, n = SS.TW.shown.dimSteps; double start = c->valA, finish = SS.TW.stepDim.finish;
for(i = 1; i <= n; i++) { SS.TW.stepDim.time = GetMilliseconds();
c = SK.GetConstraint(hc); SS.TW.stepDim.step = 1;
c->valA = start + ((finish - start)*i)/n;
if(!SS.TW.stepDim.timer) {
SS.TW.stepDim.timer = Platform::CreateTimer();
}
SS.TW.stepDim.timer->onTimeout = [=] {
if(SS.TW.stepDim.step <= SS.TW.stepDim.steps) {
c->valA = start + ((finish - start)*SS.TW.stepDim.step)/SS.TW.stepDim.steps;
SS.MarkGroupDirty(c->group); SS.MarkGroupDirty(c->group);
SS.GenerateAll(); SS.GenerateAll();
if(!SS.ActiveGroupsOkay()) { if(!SS.ActiveGroupsOkay()) {
// Failed to solve, so quit // Failed to solve, so quit
break; return;
} }
SS.GW.window->Redraw(); SS.TW.stepDim.step++;
const int64_t STEP_MILLIS = 50;
int64_t time = GetMilliseconds();
if(time - SS.TW.stepDim.time < STEP_MILLIS) {
SS.TW.stepDim.timer->RunAfterNextFrame();
} else {
SS.TW.stepDim.timer->RunAfter(time - SS.TW.stepDim.time - STEP_MILLIS);
} }
SS.TW.stepDim.time = time;
} else {
SS.TW.GoToScreen(Screen::LIST_OF_GROUPS);
SS.ScheduleShowTW();
} }
SS.GW.Invalidate(); SS.GW.Invalidate();
SS.TW.GoToScreen(Screen::LIST_OF_GROUPS); };
SS.TW.stepDim.timer->RunAfterNextFrame();
}
} }
void TextWindow::ShowStepDimension() { void TextWindow::ShowStepDimension() {
Constraint *c = SK.constraint.FindByIdNoOops(shown.constraint); Constraint *c = SK.constraint.FindByIdNoOops(shown.constraint);
@ -593,17 +612,17 @@ void TextWindow::ShowStepDimension() {
Printf(true, "%FtSTEP DIMENSION%E %s", c->DescriptionString().c_str()); Printf(true, "%FtSTEP DIMENSION%E %s", c->DescriptionString().c_str());
if(shown.dimIsDistance) { if(stepDim.isDistance) {
Printf(true, "%Ba %Ftstart%E %s", SS.MmToString(c->valA).c_str()); Printf(true, "%Ba %Ftstart%E %s", SS.MmToString(c->valA).c_str());
Printf(false, "%Bd %Ftfinish%E %s %Fl%Ll%f[change]%E", Printf(false, "%Bd %Ftfinish%E %s %Fl%Ll%f[change]%E",
SS.MmToString(shown.dimFinish).c_str(), &ScreenStepDimFinish); SS.MmToString(stepDim.finish).c_str(), &ScreenStepDimFinish);
} else { } else {
Printf(true, "%Ba %Ftstart%E %@", c->valA); Printf(true, "%Ba %Ftstart%E %@", c->valA);
Printf(false, "%Bd %Ftfinish%E %@ %Fl%Ll%f[change]%E", Printf(false, "%Bd %Ftfinish%E %@ %Fl%Ll%f[change]%E",
shown.dimFinish, &ScreenStepDimFinish); stepDim.finish, &ScreenStepDimFinish);
} }
Printf(false, "%Ba %Ftsteps%E %d %Fl%Ll%f%D[change]%E", Printf(false, "%Ba %Ftsteps%E %d %Fl%Ll%f%D[change]%E",
shown.dimSteps, &ScreenStepDimSteps); stepDim.steps, &ScreenStepDimSteps);
Printf(true, " %Fl%Ll%fstep dimension now%E", &ScreenStepDimGo); Printf(true, " %Fl%Ll%fstep dimension now%E", &ScreenStepDimGo);
@ -762,16 +781,16 @@ void TextWindow::EditControlDone(std::string s) {
case Edit::STEP_DIM_FINISH: case Edit::STEP_DIM_FINISH:
if(Expr *e = Expr::From(s, /*popUpError=*/true)) { if(Expr *e = Expr::From(s, /*popUpError=*/true)) {
if(shown.dimIsDistance) { if(stepDim.isDistance) {
shown.dimFinish = SS.ExprToMm(e); stepDim.finish = SS.ExprToMm(e);
} else { } else {
shown.dimFinish = e->Eval(); stepDim.finish = e->Eval();
} }
} }
break; break;
case Edit::STEP_DIM_STEPS: case Edit::STEP_DIM_STEPS:
shown.dimSteps = min(300, max(1, atoi(s.c_str()))); stepDim.steps = min(300, max(1, atoi(s.c_str())));
break; break;
case Edit::TANGENT_ARC_RADIUS: case Edit::TANGENT_ARC_RADIUS:

View File

@ -272,9 +272,6 @@ public:
hStyle style; hStyle style;
hConstraint constraint; hConstraint constraint;
bool dimIsDistance;
double dimFinish;
int dimSteps;
struct { struct {
int times; int times;
@ -435,6 +432,15 @@ public:
static void ScreenAllowRedundant(int link, uint32_t v); static void ScreenAllowRedundant(int link, uint32_t v);
struct {
bool isDistance;
double finish;
int steps;
Platform::TimerRef timer;
int64_t time;
int step;
} stepDim;
static void ScreenStepDimSteps(int link, uint32_t v); static void ScreenStepDimSteps(int link, uint32_t v);
static void ScreenStepDimFinish(int link, uint32_t v); static void ScreenStepDimFinish(int link, uint32_t v);
static void ScreenStepDimGo(int link, uint32_t v); static void ScreenStepDimGo(int link, uint32_t v);
@ -572,8 +578,11 @@ public:
Vector ProjectPoint4(Vector p, double *w); Vector ProjectPoint4(Vector p, double *w);
Vector UnProjectPoint(Point2d p); Vector UnProjectPoint(Point2d p);
Vector UnProjectPoint3(Vector p); Vector UnProjectPoint3(Vector p);
Platform::TimerRef animateTimer;
void AnimateOnto(Quaternion quatf, Vector offsetf); void AnimateOnto(Quaternion quatf, Vector offsetf);
void AnimateOntoWorkplane(); void AnimateOntoWorkplane();
Vector VectorFromProjs(Vector rightUpForward); Vector VectorFromProjs(Vector rightUpForward);
void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin, void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin,
double *wmin, bool usePerspective, double *wmin, bool usePerspective,