From 5457e76636d166c6ecc50d424d1ba7df61655f2e Mon Sep 17 00:00:00 2001
From: Andrii <andriyndev@gmail.com>
Date: Thu, 8 Aug 2024 00:55:55 +0300
Subject: [PATCH] Implement batched signals for Digiline Chests

---
 inventory.lua | 40 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/inventory.lua b/inventory.lua
index 0004954..624b9de 100644
--- a/inventory.lua
+++ b/inventory.lua
@@ -2,6 +2,11 @@ local S = digilines.S
 
 local pipeworks_enabled = minetest.get_modpath("pipeworks") ~= nil
 
+-- Signals which will be sent in a single batch
+local batched_signals = {}
+-- Maximum interval from the previous signal to include the current one into batch (in seconds)
+local interval_to_batch = 0.1
+
 -- Sends a message onto the Digilines network.
 -- pos: the position of the Digilines chest node.
 -- action: the action string indicating what happened.
@@ -10,7 +15,9 @@ local pipeworks_enabled = minetest.get_modpath("pipeworks") ~= nil
 -- to_slot: the slot number that is put into (optional).
 -- side: which side of the chest the action occurred (optional).
 local function send_message(pos, action, stack, from_slot, to_slot, side)
-	local channel = minetest.get_meta(pos):get_string("channel")
+	local meta = minetest.get_meta(pos)
+	local channel = meta:get_string("channel")
+
 	local msg = {
 		action = action,
 		stack = stack and stack:to_table(),
@@ -19,6 +26,20 @@ local function send_message(pos, action, stack, from_slot, to_slot, side)
 		-- Duplicate the vector in case the caller expects it not to change.
 		side = side and vector.new(side)
 	}
+
+	-- Check if we need to include the current signal into batch
+	-- Store "prev_time" in metadata as a string to avoid integer overflow
+	local prev_time = tonumber(meta:get_string("prev_time"))
+	local cur_time = minetest.get_us_time()
+	meta:set_string("prev_time", tostring(cur_time))
+	if cur_time - prev_time < 1000000 * interval_to_batch then
+		local pos_text = vector.to_string(pos)
+		batched_signals[pos_text] = batched_signals[pos_text] or {}
+		table.insert(batched_signals[pos_text], msg)
+		minetest.get_node_timer(pos):start(interval_to_batch)
+		return
+	end
+
 	digilines.receptor_send(pos, digilines.rules.default, channel, msg)
 end
 
@@ -186,6 +207,9 @@ minetest.register_node("digilines:chest", {
 		local inv = meta:get_inventory()
 		inv:set_size("main", 8*4)
 	end,
+	on_destruct = function(pos)
+		batched_signals[vector.to_string(pos)] = nil
+	end,
 	after_place_node = tubescan,
 	after_dig_node = tubescan,
 	can_dig = function(pos)
@@ -321,6 +345,20 @@ minetest.register_node("digilines:chest", {
 		send_message(pos, "utake", stack, index)
 		check_empty(pos)
 		minetest.log("action", player:get_player_name().." takes stuff from chest at "..minetest.pos_to_string(pos))
+	end,
+	on_timer = function(pos, elapsed)
+		local channel = minetest.get_meta(pos):get_string("channel")
+		local pos_text = vector.to_string(pos)
+		if #batched_signals[pos_text] == 1 then
+			-- If there is only one signal is the batch, don't send it in a batch
+			digilines.receptor_send(pos, digilines.rules.default, next(batched_signals[pos_text]))
+		else
+			digilines.receptor_send(pos, digilines.rules.default, channel, {
+				action = "batch",
+				signals = batched_signals[pos_text]
+			})
+		end
+		batched_signals[pos_text] = nil
 	end
 })