aboutsummaryrefslogtreecommitdiff
path: root/src/rollback.h
blob: 1d9949d1520676094d5c548dc0a9dc3de8ee5f26 (plain)
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
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#pragma once

#include <string>
#include "irr_v3d.h"
#include "rollback_interface.h"
#include <list>
#include <vector>
#include "sqlite3.h"

class IGameDef;

struct ActionRow;
struct Entity;

class RollbackManager: public IRollbackManager
{
public:
	RollbackManager(const std::string & world_path, IGameDef * gamedef);
	~RollbackManager();

	void reportAction(const RollbackAction & action_);
	std::string getActor();
	bool isActorGuess();
	void setActor(const std::string & actor, bool is_guess);
	std::string getSuspect(v3s16 p, float nearness_shortcut,
			float min_nearness);
	void flush();

	void addAction(const RollbackAction & action);
	std::list<RollbackAction> getEntriesSince(time_t first_time);
	std::list<RollbackAction> getNodeActors(v3s16 pos, int range,
			time_t seconds, int limit);
	std::list<RollbackAction> getRevertActions(
			const std::string & actor_filter, time_t seconds);

private:
	void registerNewActor(const int id, const std::string & name);
	void registerNewNode(const int id, const std::string & name);
	int getActorId(const std::string & name);
	int getNodeId(const std::string & name);
	const char * getActorName(const int id);
	const char * getNodeName(const int id);
	bool createTables();
	bool initDatabase();
	bool registerRow(const ActionRow & row);
	const std::list<ActionRow> actionRowsFromSelect(sqlite3_stmt * stmt);
	ActionRow actionRowFromRollbackAction(const RollbackAction & action);
	const std::list<RollbackAction> rollbackActionsFromActionRows(
			const std::list<ActionRow> & rows);
	const std::list<ActionRow> getRowsSince(time_t firstTime,
			const std::string & actor);
	const std::list<ActionRow> getRowsSince_range(time_t firstTime, v3s16 p,
			int range, int limit);
	const std::list<RollbackAction> getActionsSince_range(time_t firstTime, v3s16 p,
			int range, int limit);
	const std::list<RollbackAction> getActionsSince(time_t firstTime,
			const std::string & actor = "");
	void migrate(const std::string & filepath);
	static float getSuspectNearness(bool is_guess, v3s16 suspect_p,
		time_t suspect_t, v3s16 action_p, time_t action_t);


	IGameDef *gamedef = nullptr;

	std::string current_actor;
	bool current_actor_is_guess = false;

	std::list<RollbackAction> action_todisk_buffer;
	std::list<RollbackAction> action_latest_buffer;

	std::string database_path;
	sqlite3 * db;
	sqlite3_stmt * stmt_insert;
	sqlite3_stmt * stmt_replace;
	sqlite3_stmt * stmt_select;
	sqlite3_stmt * stmt_select_range;
	sqlite3_stmt * stmt_select_withActor;
	sqlite3_stmt * stmt_knownActor_select;
	sqlite3_stmt * stmt_knownActor_insert;
	sqlite3_stmt * stmt_knownNode_select;
	sqlite3_stmt * stmt_knownNode_insert;

	std::vector<Entity> knownActors;
	std::vector<Entity> knownNodes;
};
nfo template yard_id = { yard_name = string, active_indicator_pos = POS(), dir_indicator_pos = POS(), error_indicator_pos = POS(), headshunt_max = number, notify = empty table, notify_pos = pos, -- reserved vars -- last_id = string (the id of the last train that entered the yard last) arrival_length = number arrival_time = string (RWT) departure_length = number or "?" (set when train arrives, until train departs) departure_time = string (RWT) rts = bool (whether the entering train will exit the way it came in) }, ]]-- TY = { yard_name = "Trisiston", active_indicator_pos = POS(-4025,14,-2659), dir_indicator_pos = POS(-4025,12,-2665), error_indicator_pos = POS(-4025,13,-2671), headshunt_max = 5, notify = {}, notify_pos = POS(-4023,12,-2660), arrival_time = rwt.now(), departure_time = rwt.now() }, BY = { yard_name = "Banach", active_indicator_pos = POS(-2002,3,-1099), dir_indicator_pos = POS(-2009,3,-1101), error_indicator_pos = POS(-1999,3,-1099), headshunt_max = 5, notify = {}, notify_pos = POS(-2004,2,-1101), arrival_time = rwt.now(), departure_time = rwt.now() }, ARC = { yard_name = "Arcadius", active_indicator_pos = POS(-1952,16,840), dir_indicator_pos = POS(-1950,16,840), error_indicator_pos = POS(-1948,16,840), headshunt_max = 5, notify = {}, notify_pos = POS(-1946,16,840), arrival_time = rwt.now(), departure_time = rwt.now() }, IP = { yard_name = "Ipswich", active_indicator_pos = POS(1179,16,3848), dir_indicator_pos = POS(1177,16,3855), error_indicator_pos = POS(1179,16,3850), headshunt_max = 5, notify = {}, notify_pos = POS(1178,16,3851), arrival_time = rwt.now(), departure_time = rwt.now() }, CAN = { yard_name = "Cannery", active_indicator_pos = POS(-594,26,2486), dir_indicator_pos = POS(-594,26,2484), error_indicator_pos = POS(-594,26,2482), headshunt_max = 2, --notify = {}, --notify_pos = POS(-594,26,2485), arrival_time = rwt.now(), departure_time = rwt.now() } -- HY = { -- active_indicator_pos = POS(-4025,14,-2659), -- dir_indicator_pos = POS(-4025,13,-2665), -- error_indicator_pos = POS(-4025,13,-2671), -- }, } S.known_trains = { ['120684'] = "LHF #1", --Maverick2797 ['249165'] = "LHF #2", --Maverick2797 ['734206'] = "ARC-BY", --Maverick2797 ['588750'] = "MMF-TY", --survivalg/erstazi ['543381'] = "WOA-IP", --Maverick2797 ['834721'] = "S27-ARC", --Maverick2797 ['513598'] = "S27EX-ARC", --Maverick2797 ['590988'] = "CAN-ARC", --Maverick2797 } end ------------------------------------------------------------------------------------ -- Utility Functions F.indicator = function(indicator,set) if set ~= nil then if type(set) == string then setstate(indicator,set) else setstate(indicator,(set and "on") or "off") end end return (getstate(indicator) == "on") or false end F.get_rc_safe = function() return get_rc() or "" end F.has_rc = function(query,rc_list) -- query = string, single entry if not atc_id then return false end if rc_list == "" or query == nil or query=="" then return false end if not rc_list then rc_list = F.get_rc_safe() end for word in rc_list:gmatch("[^%s]+") do if word == query then return true end end return false end F.has_rc_match = function(query,rc_list) -- query = pattern string, single entry if not atc_id then return false end if rc_list == "" or query == nil or query=="" then return false end if not rc_list then rc_list = F.get_rc_safe() end local rc = {} for v in rc_list:gmatch("("..query..")") do table.insert(rc,v) end if rc[1] == true then return true, rc else return nil end end F.add_rc = function(rc_list) -- rc_list = string or table, eg: {"rc1","rc2"} OR "rc1 rc2" if not atc_id then return false end if type(rc_list) == "table" then rc_list = table.concat(rc_list," ") end set_rc(F.get_rc_safe().." "..rc_list) return true end F.remove_rc = function(rc_list,arrow_mode) -- rc_list = string eg: "rc1 rc2 rc3" OR table eg: {"rc1","rc2","rc3"} -- Arrow Modes: -- true: with arrow direction -- false: against arrow direction -- nil: ignores arrow direction if not atc_id then return false end if not rc_list then return false end if (arrow_mode == nil) or (atc_arrow == arrow_mode) then -- prep rc_list to useable format local rc_remove = {} if type(rc_list) == "string" then for word in rc_list:gmatch("[^%s]+") do rc_remove[word] = true end elseif type(rc_list) == "table" then for _,word in pairs(rc_list) do rc_remove[word] = true end end -- remove codes from train's rc local rc = F.get_rc_safe() local reinsert = {} for token in rc:gmatch("[^%s]+") do if not rc_remove[token] then table.insert(reinsert,token) end end -- insert new string to train's rc set_rc(table.concat(reinsert," ")) end return reinsert end F.remove_rc_match = function(rc_list) -- rc_list = pattern string, single entry, eg: "rc_%d+" if not atc_id then return false end if not rc_list then return false end local rm = {} for v in F.get_rc_safe():gmatch("("..rc_list..")") do table.insert(rm,v) end F.remove_rc(rm) return rm end ---------------------------------------------------------------------------------------------- -- Trackside Functions -- this_dir = points towards Origin/Junction Yard F.yard_arrival = function(yard_id,this_dir, force_rts) -- arrow points towards yard local yard = S.yards[yard_id] --yard ref if F.has_rc(yard_id.."_NOSHUNT") then return end local function enter_yard() if not atc_id then F.indicator(yard.error_indicator_pos,true) return end F.indicator(yard.dir_indicator_pos,this_dir) F.indicator(yard.active_indicator_pos,true) F.add_rc({yard_id.."_ARRIVE"}) local rts = false if force_rts then -- yard is designated as a terminus yard. all trains MUST rts F.add_rc(yard_id.."_RTS") rts = true elseif F.has_rc(yard_id.."_RTS") then --save the RTS flag as it's removed during the arrival procedure F.add_rc({yard_id.."_HAS_RTS"}) rts = true end atc_set_ars_disable(false) atc_send("S6") S.yards[yard_id].last_id = atc_id S.yards[yard_id].arrival_length = train_length() S.yards[yard_id].arrival_time = rwt.now() S.yards[yard_id].departure_length = "?" S.yards[yard_id].departure_time = rwt.now() S.yards[yard_id].rts = rts if S.print_debug then print(rwt.to_string(rwt.now())) print("YARD "..yard_id..": Train "..atc_id.." ("..(S.known_trains[atc_id] or "Unknown")..") enters from the "..tostring(this_dir).." direction and will exit in the "..tostring(rts).." direction") print("YARD "..yard_id..": Length "..train_length()) end return end __approach_callback_mode = 1 if event.approach and not event.has_entered then atc_set_ars_disable(true) atc_set_lzb_tsr(1) return end if event.train and atc_arrow then if F.indicator(yard.active_indicator_pos) then if S.print_debug then print(rwt.to_string(rwt.now())) print("YARD "..yard_id..": Train "..atc_id.." ("..(S.known_trains[atc_id] or "Unknown")..") has arrived from the "..tostring(this_dir).." direction and has to wait for the yard to deactivate.") end schedule_in(";10","recheck") return else enter_yard() return true end end if event.schedule then if F.indicator(yard.active_indicator_pos) then schedule_in(";10","recheck") return else enter_yard() return true end end end F.classification = function(yard_id, this_dir) -- arrow points towards headshunt local yard = S.yards[yard_id] --yard ref if not F.indicator(yard.active_indicator_pos) then return end if F.has_rc(yard_id.."_NOSHUNT") then return end -- this_dir == true for north end, false for south end if F.indicator(yard.active_indicator_pos) then if atc_arrow then -- loco is at working end F.remove_rc({yard_id.."_PICKUP"}) if F.has_rc(yard_id.."_ARRIVE") and F.indicator(yard.dir_indicator_pos) == this_dir then --first pass, prep train for working F.remove_rc({yard_id.."_AROUND"})