aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/schedule.c
blob: 7fbfaf55a82924fb0a9c7f770c924f2d430ebf68 (plain) (blame)
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/* SPDX-License-Identifier: MIT-0 */

#include <assert.h>
#include <math.h>
#include <stdbool.h>

#include "schedule.h"

#define STOPLEN_ROUGH 10
#define STOPLEN_FINE 1

int SWITCH_INTERMEDIATE = SWITCH_INTERMEDIATE_DEFAULT;

static void emit_waypoint(const decostate_t *ds, segtype_t type, const waypoint_callback_t *wp_cb)
{
    if (wp_cb && wp_cb->fn)
        wp_cb->fn(ds, type, wp_cb->arg);
}

const gas_t *best_gas(double depth, const gas_t *gasses, int nof_gasses)
{
    const gas_t *best = NULL;
    double mod_best = -1;

    for (int i = 0; i < nof_gasses; i++) {
        double mod = gas_mod(&gasses[i]);

        if (depth - mod < 1E-2 && (mod_best == -1 || mod < mod_best)) {
            best = &gasses[i];
            mod_best = mod;
        }
    }

    return best;
}

int direct_ascent(const decostate_t *ds, double ascrate)
{
    decostate_t ds_ = *ds;
    assert(ds_.firststop == -1);

    add_segment_ascdec(&ds_, ds_.depth, abs_depth(0), gauge_depth(ds_.depth) / ascrate, ds_.gas);

    return gauge_depth(ceiling(&ds_, ds_.gfhi)) <= 0;
}

void simulate_dive(decostate_t *ds, const waypoint_t *waypoints, int nof_waypoints, const waypoint_callback_t *wp_cb)
{
    for (int i = 0; i < nof_waypoints; i++) {
        double d = waypoints[i].depth;
        double t = waypoints[i].time;
        const gas_t *g = waypoints[i].gas;

        if (d != ds->depth)
            add_segment_ascdec(ds, ds->depth, d, t, g);
        else
            add_segment_const(ds, d, t, g);

        emit_waypoint(ds, SEG_DIVE, wp_cb);
    }
}

double calc_ndl(decostate_t *ds, double ascrate)
{
    double ndl = 0;

    /* rough steps */
    decostate_t ds_ = *ds;

    while (ndl < 360) {
        add_segment_const(&ds_, ds_.depth, STOPLEN_ROUGH, ds_.gas);

        if (!direct_ascent(&ds_, ascrate))
            break;

        ndl = ds_.runtime - ds->runtime;
    }

    /* fine steps */
    ds_ = *ds;

    if (ndl)
        add_segment_const(&ds_, ds_.depth, ndl, ds_.gas);

    while (ndl < 360) {
        add_segment_const(&ds_, ds_.depth, STOPLEN_FINE, ds_.gas);

        if (!direct_ascent(&ds_, ascrate))
            break;

        ndl = ds_.runtime - ds->runtime;
    }

    return ndl;
}

static void deco_stop(decostate_t *ds, double next_stop, double current_gf)
{
    double stoplen = 0;

    /* rough steps */
    decostate_t ds_ = *ds;

    for (;;) {
        add_segment_const(&ds_, ds_.depth, STOPLEN_ROUGH, ds_.gas);

        if (ceiling(&ds_, current_gf) <= next_stop)
            break;

        stoplen = ds_.runtime - ds->runtime;
    }

    if (stoplen)
        add_segment_const(ds, ds->depth, stoplen, ds->gas);

    /* fine steps */
    while (ceiling(ds, current_gf) > next_stop)
        add_segment_const(ds, ds->depth, STOPLEN_FINE, ds->gas);
}

static int surfaced(decostate_t *ds)
{
    return ds->depth - SURFACE_PRESSURE < 1E-2;
}

decoinfo_t calc_deco(decostate_t *ds, const gas_t *deco_gasses, int nof_gasses, const waypoint_callback_t *wp_cb)
{
    const double runtime_start = ds->runtime;
    const double asc_per_min = msw_to_bar(9);

    double next_stop;
    double current_gf;
    const gas_t *best;

    bool deco_started = false;

    /* check if direct ascent is possible */
    if (direct_ascent(ds, asc_per_min))
        return (decoinfo_t){.tts = 0, .ndl = calc_ndl(ds, asc_per_min)};

    /* prepare for the first stop */
    next_stop = abs_depth(ds->ceil_multiple * (ceil(gauge_depth(ds->depth) / ds->ceil_multiple)));

    while (next_stop >= ds->depth)
        next_stop -= ds->ceil_multiple;

    current_gf = get_gf(ds, next_stop);

    /* alternate between ascending and stopping until we surface */
    for (;;) {
        /* ascend */
        while (ceiling(ds, current_gf) < next_stop && !surfaced(ds)) {
            /* switch to better gas if available */
            best = best_gas(ds->depth, deco_gasses, nof_gasses);

            if (SWITCH_INTERMEDIATE && best && best != ds->gas) {
                /* emit waypoint because we're about to switch gas */
                emit_waypoint(ds, deco_started ? SEG_TRAVEL : SEG_ASCENT, wp_cb);

                /* switch gas */
                add_segment_const(ds, ds->depth, 1, best);
                emit_waypoint(ds, SEG_GAS_SWITCH, wp_cb);

                continue;
            }

            /* ascend to next stop */
            add_segment_ascdec(ds, ds->depth, next_stop, fabs(ds->depth - next_stop) / asc_per_min, ds->gas);

            /* make next stop shallower */
            next_stop -= ds->ceil_multiple;

            if (LAST_STOP_AT_SIX && next_stop < abs_depth(msw_to_bar(6)))
                next_stop = SURFACE_PRESSURE;

            /* recalculate gf */
            current_gf = get_gf(ds, next_stop);
        }

        if (ds->firststop == -1) {
            ds->firststop = ds->depth;

            /*
             * if the first stop wasn't know yet during the previous call to
             * get_gf, the result was inaccurate and needs to be recalculated
             */
            current_gf = get_gf(ds, next_stop);

            /* if the new gf also allows us to ascend further, continue ascending */
            if (ceiling(ds, current_gf) < next_stop)
                continue;
        }

        /* terminate if we surfaced */
        if (surfaced(ds)) {
            emit_waypoint(ds, SEG_SURFACE, wp_cb);
            return (decoinfo_t){.ndl = 0, .tts = ds->runtime - runtime_start};
        }

        emit_waypoint(ds, deco_started ? SEG_TRAVEL : SEG_ASCENT, wp_cb);
        deco_started = true;

        /* switch to better gas if available */
        best = best_gas(ds->depth, deco_gasses, nof_gasses);

        if (best)
            ds->gas = best;

        /* stop until ceiling rises above next stop */
        deco_stop(ds, next_stop, current_gf);
        emit_waypoint(ds, SEG_DECO_STOP, wp_cb);
    }
}