Skip to content
Open
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
89 changes: 34 additions & 55 deletions src/web/Main.re
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,35 @@ let restart_caret_animation = () =>

let apply =
(
model: Logged.Model.t,
action: Logged.Update.t,
model: CrashHandling.Model.t,
action: CrashHandling.Update.t,
~schedule_action,
~schedule_autosave,
)
: Logged.Model.t => {
: CrashHandling.Model.t => {
restart_caret_animation();

/* This function is split into two phases, update and calculate.
The intention is that eventually, the calculate phase will be
done automatically by incremental calculation. */
// ---------- UPDATE PHASE ----------
let updated: Updated.t(Logged.Model.t) =
try(
Logged.Update.update(
~import_log=Log.import,
~get_log_and=Log.get_and,
~schedule_action,
action,
model,
)
) {
| Haz3lcore.Action.Failure.Exception(t) =>
Printf.printf(
"ERROR: Action.Failure: %s\n",
t |> Haz3lcore.Action.Failure.show,
);
model |> Updated.return_quiet;
| exc =>
Printf.printf(
"ERROR: Exception during apply: %s\n",
Printexc.to_string(exc),
);
model |> Updated.return_quiet;
};
let updated: Updated.t(CrashHandling.Model.t) =
CrashHandling.Update.update(
~import_log=Log.import,
~get_log_and=Log.get_and,
~schedule_action,
action,
model,
);
// ---------- CALCULATE PHASE ----------
let model' =
try(
updated.model
|> Logged.Update.calculate(
~schedule_action,
~is_edited=updated.is_edit,
~dynamics=true,
)
) {
| exc =>
Printf.printf(
"ERROR: Exception during calculate: %s\n",
Printexc.to_string(exc),
);
{
...model,
replay_toggle: false,
};
};
CrashHandling.Update.calculate(
~schedule_action,
~is_edited=updated.is_edit,
~dynamics=true,
model,
updated.model,
);

if (updated.is_edit) {
schedule_autosave(
Expand All @@ -98,8 +72,8 @@ let start = {
let%sub save_scheduler = BonsaiUtil.Alarm.alarm;
let%sub (app_model, app_inject) =
Bonsai.state_machine1(
(module Logged.Model),
(module Logged.Update),
(module CrashHandling.Model),
(module CrashHandling.Update),
~apply_action=
(~inject, ~schedule_event, input) => {
let schedule_action = x => schedule_event(inject(x));
Expand All @@ -112,12 +86,13 @@ let start = {
apply(~schedule_action, ~schedule_autosave);
},
~default_model=
Logged.Model.load()
|> Logged.Update.calculate(
~schedule_action=_ => (),
~is_edited=true,
~dynamics=false,
),
CrashHandling.Update.calculate(
~schedule_action=_ => (),
~is_edited=true,
~dynamics=false,
CrashHandling.Model.load(),
CrashHandling.Model.load(),
),
save_scheduler,
);

Expand All @@ -130,7 +105,7 @@ let start = {
let%map app_inject = app_inject
and model = app_model;
Ui_effect.Many(
model.replay_toggle
model.model.replay_toggle
? [app_inject(Page.Update.Globals(Log(NextLog)))] : [],
);
};
Expand Down Expand Up @@ -220,7 +195,7 @@ let start = {
let _ = Haz3lcore.ProbePerform.FocusEffect.execute();
/* Update floating elements (backpack) to viewport coordinates */
FloatingElement.update_all();
model.current.current.globals.settings.core.statics
model.model.current.current.globals.settings.core.statics
? Animation.go() : ();
},
(),
Expand All @@ -232,7 +207,11 @@ let start = {
let%arr app_model = app_model
and app_inject = app_inject;
try(
Logged.View.view(app_model, ~inject=app_inject, ~get_log_and=Log.get_and)
CrashHandling.View.view(
~get_log_and=Log.get_and,
~inject=app_inject,
app_model,
)
) {
| exc =>
print_endline(
Expand Down
250 changes: 250 additions & 0 deletions src/web/app/CrashHandling.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
open Util;

type current_exception =
| Update(string)
| Calculate(string);

let last_exception: ref(option(exn)) = ref(None);
let current_exception: ref(option(current_exception)) = ref(None);

let set_last_exception = exn => {
last_exception := Some(exn);
};

let clear_last_exception = () => {
last_exception := None;
};

let set_current_exception = exn_type => {
current_exception := Some(exn_type);
};

let clear_current_exception = () => {
current_exception := None;
};

module Model = {
[@deriving (sexp, yojson)]
type state = Logged.Model.t;

[@deriving (sexp, yojson)]
type t = {model: state};

let equal = (===);

let load = () => {model: Logged.Model.load()};
};

module Update = {
[@deriving (sexp, yojson)]
type t = Logged.Update.t;

let update =
(
~import_log,
~get_log_and,
~schedule_action: t => unit,
action: t,
model: Model.t,
)
: Updated.t(Model.t) =>
switch (action) {
| Globals(ClearException) =>
clear_last_exception();
clear_current_exception();
model |> Updated.return_quiet;
| Globals(RethrowException) =>
switch (last_exception^) {
| None => model |> Updated.return_quiet
| Some(exn) => raise(exn)
}
| _ when current_exception^ == None =>
try({
let updated =
Logged.Update.update(
~import_log,
~get_log_and,
~schedule_action,
action,
model.model,
);
{
...updated,
model: {
model: updated.model,
},
};
}) {
| Haz3lcore.Action.Failure.Exception(t) =>
Printf.printf(
"ERROR: Action.Failure: %s\n",
t |> Haz3lcore.Action.Failure.show,
);
model |> Updated.return_quiet;
| Updated.InvalidAction =>
print_endline("cannot perform action");
model |> Updated.return_quiet;
| exn =>
set_last_exception(exn);
let msg = Printexc.to_string(exn);
print_endline("CrashHandling: Caught exception in update: " ++ msg);
set_current_exception(Update(msg));
model |> Updated.return_quiet;
}
| _ => model |> Updated.return_quiet
};

let calculate =
(
~schedule_action: t => unit,
~is_edited: bool,
~dynamics,
previous_model: Model.t,
model: Model.t,
)
: Model.t =>
try({
model:
model.model
|> Logged.Update.calculate(~schedule_action, ~is_edited, ~dynamics),
}) {
| exn =>
set_last_exception(exn);
let msg = Printexc.to_string(exn);
print_endline("CrashHandling: Caught exception in calculate: " ++ msg);
set_current_exception(Calculate(msg));
previous_model;
};
};

module View = {
open Virtual_dom.Vdom;
open WebUtil.Node;

let hsod_view =
(
~title: string,
~msg: string,
~inject_backtrack: Ui_effect.t(unit),
~inject_rethrow: Ui_effect.t(unit),
) =>
div(
~attrs=[Attr.class_("hsod-container")],
[
div(
~attrs=[Attr.class_("hsod")],
[
div(
~attrs=[Attr.class_("hsod-inner")],
[
div(
~attrs=[Attr.class_("hsod-img")],
[
Node.img(
~attrs=[
Attr.create("src", "img/dead-hazel.png"),
Attr.create("alt", "dead hazel"),
],
(),
),
],
),
div(
~attrs=[Attr.class_("hsod-body")],
[
h1([Node.text(title)]),
pre([Node.text(msg)]),
div(
~attrs=[Attr.class_("hsod-links")],
[
// button(
// ~attrs=[
// Attr.create("type", "button"),
// Attr.class_("hsod-button"),
// Attr.on_click(_ => {
// let confirmed =
// JsUtil.confirm(
// "Are you SURE you want to reset Hazel to its initial state? You will lose any existing code that you have written!",
// );
// if (confirmed) {
// JsUtil.clear_localstore();
// Js_of_ocaml.Dom_html.window##.location##reload;
// };
// Virtual_dom.Vdom.Effect.Ignore;
// }),
// ],
// [Node.text("Reset Hazel")],
// ),
a(
~attrs=[
Attr.create(
"href",
"https://github.com/hazelgrove/hazel/issues/new",
),
Attr.create("target", "_blank"),
Attr.class_("hsod-link"),
],
[Node.text("Report this issue on GitHub")],
),
button(
~attrs=[
Attr.create("type", "button"),
Attr.classes([
"hsod-button",
"hsod-button-primary",
]),
Attr.on_click(_ => inject_backtrack),
],
[Node.text("Revert to previous state")],
),
button(
~attrs=[
Attr.create("type", "button"),
Attr.class_("hsod-button"),
Attr.on_click(_ => inject_rethrow),
],
[Node.text("Rethrow exception")],
),
],
),
],
),
],
),
],
),
],
);

let view =
(~get_log_and, ~inject: Update.t => Ui_effect.t(unit), model: Model.t) =>
switch (current_exception^) {
| None =>
try(Logged.View.view(~get_log_and, ~inject, model.model)) {
| exn =>
set_last_exception(exn);
let msg = Printexc.to_string(exn);
set_current_exception(Update(msg));
hsod_view(
~title="Exception during View",
~msg,
~inject_backtrack=inject(Globals(Undo)),
~inject_rethrow=inject(Globals(RethrowException)),
);
}
| Some(Update(msg)) =>
hsod_view(
~title="Exception during Update",
~msg,
~inject_backtrack=inject(Globals(ClearException)),
~inject_rethrow=inject(Globals(RethrowException)),
)
| Some(Calculate(msg)) =>
hsod_view(
~title="Exception during Calculate",
~msg,
~inject_backtrack=inject(Globals(ClearException)),
~inject_rethrow=inject(Globals(RethrowException)),
)
};
};
Loading