File size: 3,490 Bytes
b7e7f16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Thin adapter around the shared `SF.createSolver()` controller.
export function createSolverController({
  sf,
  backend,
  statusBar,
  onPlan,
  onAnalysis,
  onMeta,
  onLifecycle,
  onError,
}) {
  const solver = sf.createSolver({
    backend,
    statusBar,
    onProgress(meta) {
      publishMeta(meta);
    },
    onSolution(snapshot, meta) {
      publishSnapshot(snapshot, meta);
    },
    onPaused(snapshot, meta) {
      publishSnapshot(snapshot, meta);
    },
    onResumed(meta) {
      publishMeta(meta);
    },
    onCancelled(snapshot, meta) {
      publishSnapshot(snapshot, meta);
    },
    onComplete(snapshot, meta) {
      publishSnapshot(snapshot, meta);
    },
    onFailure(_message, meta, snapshot, analysis) {
      publishSnapshot(snapshot, meta);
      if (analysis) onAnalysis(analysis);
    },
    onAnalysis(analysis) {
      onAnalysis(analysis);
      publishLifecycle();
    },
    onError(message) {
      if (typeof onError === 'function') onError(message);
      publishLifecycle();
    },
  });

  return {
    // Starts a new solve after cleaning up any previous terminal retained job.
    async start(planProvider) {
      if (solver.isRunning() || solver.getLifecycleState() === 'PAUSED') return;
      await cleanupTerminalJob();
      const plan = await planProvider();
      await solver.start(plan);
      publishMeta({
        id: solver.getJobId(),
        jobId: solver.getJobId(),
        lifecycleState: solver.getLifecycleState(),
        currentScore: null,
        bestScore: null,
        telemetry: null,
      });
    },
    pause() {
      return solver.pause().then(() => {
        publishLifecycle();
      });
    },
    resume() {
      return solver.resume().then(() => {
        publishLifecycle();
      });
    },
    cancel() {
      return solver.cancel().then(() => {
        publishLifecycle();
      });
    },
    analyzeSnapshot() {
      return solver.analyzeSnapshot().then((analysis) => {
        onAnalysis(analysis);
        publishLifecycle();
        return analysis;
      });
    },
    getLifecycleState() {
      return solver.getLifecycleState();
    },
    getJobId() {
      return solver.getJobId();
    },
    getSnapshotRevision() {
      return solver.getSnapshotRevision();
    },
  };

  // Publishes the latest solution and lifecycle metadata back to the app shell.
  function publishSnapshot(snapshot, meta) {
    if (snapshot && snapshot.solution) onPlan(snapshot.solution);
    publishMeta(meta);
  }

  // Publishes status metadata and refreshes the shell lifecycle markers.
  function publishMeta(meta) {
    onMeta(meta || null);
    publishLifecycle();
  }

  // Keeps HTML data attributes in sync with the underlying solver lifecycle.
  function publishLifecycle() {
    onLifecycle({
      jobId: solver.getJobId(),
      snapshotRevision: solver.getSnapshotRevision(),
      lifecycleState: solver.getLifecycleState(),
    });
  }

  // Deletes old terminal jobs so "Solve" always starts from a clean retained slot.
  function cleanupTerminalJob() {
    const state = solver.getLifecycleState();
    if (!solver.getJobId() || state === 'IDLE' || state === 'PAUSED' || solver.isRunning()) {
      return Promise.resolve();
    }
    return solver.delete()
      .then(() => {
        onAnalysis(null);
        onMeta(null);
        publishLifecycle();
      })
      .catch((error) => {
        if (typeof onError === 'function') onError(error);
        throw error;
      });
  }
}