Passed
Push — master ( dce7e6...e2e6ab )
by Vincent
06:59 queued 12s
created

init(Logger)   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.1481

Importance

Changes 0
Metric Value
cc 2
eloc 5
dl 0
loc 7
ccs 2
cts 3
cp 0.6667
crap 2.1481
rs 10
c 0
b 0
f 0
1
/*
2
 * This file is part of Araknemu.
3
 *
4
 * Araknemu is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * Araknemu is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with Araknemu.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2017-2021 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game;
21
22
import fr.quatrevieux.araknemu.core.event.Dispatcher;
23
import fr.quatrevieux.araknemu.core.event.EventsSubscriber;
24
import fr.quatrevieux.araknemu.core.event.Listener;
25
import fr.quatrevieux.araknemu.game.event.GameSaved;
26
import fr.quatrevieux.araknemu.game.event.GameStopped;
27
import fr.quatrevieux.araknemu.game.event.SavingGame;
28
import fr.quatrevieux.araknemu.game.player.GamePlayer;
29
import fr.quatrevieux.araknemu.game.player.PlayerService;
30
import fr.quatrevieux.araknemu.util.ExecutorFactory;
31
import org.apache.logging.log4j.Logger;
32
33
import java.time.Duration;
34
import java.util.concurrent.ScheduledExecutorService;
35
import java.util.concurrent.TimeUnit;
36
import java.util.concurrent.atomic.AtomicBoolean;
37
38
/**
39
 * Handle world saving
40
 */
41
public final class SavingService implements EventsSubscriber, PreloadableService {
42
    private final PlayerService playerService;
43
    private final GameConfiguration configuration;
44
    private final Dispatcher dispatcher;
45
46 1
    private final AtomicBoolean inProgress = new AtomicBoolean(false);
47 1
    private final ScheduledExecutorService executor = ExecutorFactory.createSingleThread();
48
49 1
    public SavingService(PlayerService playerService, GameConfiguration configuration, Dispatcher dispatcher) {
50 1
        this.playerService = playerService;
51 1
        this.configuration = configuration;
52 1
        this.dispatcher = dispatcher;
53 1
    }
54
55
    @Override
56
    public void init(Logger logger) {
57 1
        if (!configuration.autosaveEnabled()) {
58
            return;
59
        }
60
61 1
        scheduleNextSave();
62 1
    }
63
64
    @Override
65
    public String name() {
66 1
        return "saving";
67
    }
68
69
    @Override
70
    public Listener[] listeners() {
71 1
        return new Listener[] {
72 1
            new Listener<GameStopped>() {
73
                @Override
74
                public void on(GameStopped event) {
75 1
                    executor.shutdownNow();
76 1
                }
77
78
                @Override
79
                public Class<GameStopped> event() {
80 1
                    return GameStopped.class;
81
                }
82
            },
83
        };
84
    }
85
86
    /**
87
     * Execute the save
88
     * Note: this method is non-blocking : the save process is launched in a thread
89
     *
90
     * @return true is the save is launched, or false if it's already in progress
91
     */
92
    public boolean execute() {
93 1
        if (inProgress.getAndSet(true)) {
94 1
            return false;
95
        }
96
97 1
        executor.execute(this::runSave);
98
99 1
        return true;
100
    }
101
102
    /**
103
     * Schedule the next autosave
104
     *
105
     * Note: do not use {@link ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, TimeUnit)}
106
     *       to prevent conflicts on long run. Instead, it will reschedule a save after the previous one
107
     */
108
    private void scheduleNextSave() {
109 1
        if (executor.isShutdown()) {
110
            return;
111
        }
112
113 1
        final Duration interval = configuration.autosaveInterval();
114
115 1
        executor.schedule(() -> {
116 1
            if (!inProgress.getAndSet(true)) {
117 1
                runSave();
118
            }
119
120 1
            scheduleNextSave();
121 1
        }, interval.toMillis(), TimeUnit.MILLISECONDS);
122 1
    }
123
124
    /**
125
     * Run the save process
126
     * Note: this method will not check the "inProgress" property : it must be checked before
127
     */
128
    private void runSave() {
129 1
        dispatcher.dispatch(new SavingGame());
130
131
        try {
132 1
            playerService.online().forEach(GamePlayer::save);
133
        } finally {
134 1
            inProgress.set(false);
135 1
            dispatcher.dispatch(new GameSaved());
136
        }
137 1
    }
138
}
139