Skip to content

Instantly share code, notes, and snippets.

@kawashirov
Last active June 29, 2024 05:20
Show Gist options
  • Save kawashirov/da4bb7eb4b7ce7560447e22f8cd79998 to your computer and use it in GitHub Desktop.
Save kawashirov/da4bb7eb4b7ce7560447e22f8cd79998 to your computer and use it in GitHub Desktop.
CC AE2 Autocraft
local pretty = require "cc.pretty"
local expect = require "cc.expect"
local LogManager = require "LogManager"
local log = LogManager.new("autocraft", 1024 * 1024, 5) -- 1MB, 5 файлов истории
log:print("Log open!")
local SIMPLIFY = {
["minecraft"] = "MC",
["modern_industrialization"] = "MI",
["techreborn"] = "TR",
}
-- -- -- "статические" функции
local function simplify_ns(s)
-- modern_industrialization:very_fucking_long_name -> MI:very_fucking_long_name
expect.expect(1, s, "string")
local simplified = s
for simplify_key, simplify_val in pairs(SIMPLIFY) do
simplified = string.gsub(simplified, simplify_key, simplify_val)
end
return simplified
end
local function iv2kv(raw_t, key_k, value_k)
-- index-value table -> key-value table
expect.expect(1, raw_t, "table")
expect.expect(2, key_k, "string")
expect.expect(3, value_k, "string")
local new_t = {}
for _, item in ipairs(raw_t) do
key = item[key_k]
assert(type(key) ~= "nil")
value = item[value_k]
new_t[key] = value
end
return new_t
end
local function kv2iv(raw_t)
-- index-value table -> key-value table
expect.expect(1, raw_t, "table")
local new_t = {}
for _, item in pairs(raw_t) do
table.insert(new_t, item)
end
return new_t
end
local function pop(t)
expect.expect(1, t, "table")
for k, v in pairs(t) do
t[k] = nil
return k, v
end
end
local function count_pairs(t)
expect.expect(1, t, "table")
-- count pairs in key-table
local count = 0
for key, value in pairs(t) do
count = count + 1
end
return count
end
-- -- -- рабочие контекстно-зависимые функции и переменные
local config = nil
local rules = {} -- правила как ключ-значение
local rules_count = 0
local rules_satisfied = 0
local ae2cc = nil -- переферал AE2CC
local ae2upw = nil -- переферал AE2 Unlimited Peripheral Works
local all_objects = {} -- все предметы в AE2 как ключ-значение
local all_objects_c = 0
local all_cpus = {} -- все профессоры в AE2
local busy_player_cpus_c = 0
local busy_cpus_c = 0
local free_cpus_c = 0
local all_cpus_c = 0
local usage_cpus = 0
local can_craft = false
local jobs_by_uuid = {}
local jobs_by_object = {}
local function update_objects(raw_t)
-- index-value table -> key-value table
all_objects = {}
all_objects_c = 0
raw_objects = ae2cc.getAvailableObjects()
for _, item in pairs(raw_objects) do
all_objects[item.id] = item
all_objects_c = all_objects_c + 1
end
end
local function update_cpus()
all_cpus = ae2cc.getCraftingCPUs()
busy_player_cpus_c, busy_cpus_c, free_cpus_c = 0, 0, 0
for _, cpu in pairs(all_cpus) do
if cpu.selectionMode == "PLAYER_ONLY" then
if cpu.jobStatus then
busy_player_cpus_c = busy_player_cpus_c + 1
end
else
if cpu.jobStatus then
busy_cpus_c = busy_cpus_c + 1
else
free_cpus_c = free_cpus_c + 1
end
end
end
all_cpus_c = busy_cpus_c + free_cpus_c
usage_cpus = busy_cpus_c / all_cpus_c
if busy == 0 then
-- Если все процессоры не загружены, значит мы точно знаем,
-- что все задачи выполнены и можно сбросить таймеры
jobs_by_uuid = {}
jobs_by_object = {}
end
local allowed_usage = busy_player_cpus_c > 0 and 4/10 or 9/10
can_craft = usage_cpus < allowed_usage and free_cpus_c > 1
end
local function get_or_alloc_rule(name, rule_type)
expect.expect(1, name, "string")
expect.expect(2, rule_type, "string")
local rule = rules[name]
if not rule then
rule = { name = name, type = rule_type, parents = {} }
rules[name] = rule
rule.scale = config.default_scale
rule.priority = 0
end
return rule
end
local function normalize_fluid_amount(amount)
expect.expect(1, amount, "number")
-- нормализация mB к слиткам.
-- 1000mB = 1B = = 1 блок = 9 слитков
return amount * (9 / 1000)
end
local function denormalize_fluid_amount(amount)
expect.expect(1, amount, "number")
-- 1000mB = 1B = = 1 блок = 9 слитков
return amount * (1000 / 9)
end
local function max_of_variants(parent_variants)
expect.expect(1, parent_variants, "table", "nil")
if not parent_variants then return 0 end
local max = -math.huge
for _, parent_variant in pairs(parent_variants) do
local parent_rule = rules[parent_variant]
local object = all_objects[parent_rule.name]
local amount = (object and object.amount) or 0
if parent_rule.type == "fluid" then
amount = normalize_fluid_amount(amount)
end
if amount > max then
max = amount
end
end
return max
end
local function min_of_parents(parents)
expect.expect(1, parents, "table", "nil")
if not parents then return 0 end
local min = math.huge
for _, parent in pairs(parents) do
local amount = 0
if type(parent) == "string" then -- это ссылка на правило
local parent_rule = rules[parent]
local object = all_objects[parent_rule.name]
amount = (object and object.amount or 0) or 0
if parent_rule.type == "fluid" then
amount = normalize_fluid_amount(amount)
end
elseif type(parent) == "table" then -- это варианты
amount = max_of_variants(parent)
else
error("Unknown parent type: " .. type(parent))
end
if amount < min then
min = amount
end
end
return min
end
local function build_rules_phase1_init(queue)
expect.expect(1, queue, "table")
for name, base_rule in pairs(config.rules) do
local rule_type = base_rule.type or "item"
local rule = get_or_alloc_rule(name, rule_type)
for opt_key, opt_val in pairs(base_rule) do
rule[opt_key] = opt_val
end
rule.explicit_scale = base_rule.scale
rule.explicit_weight = base_rule.weight
table.insert(queue, rule)
end
end
local function build_rules_phase1_entry(queue, rule)
expect.expect(1, queue, "table")
expect.expect(2, rule, "table")
local datas = ae2upw.getPatternsFor(rule.type, rule.name)
if #datas > 1 then
log:error(("Two ways found for: %s"):format(rule.name))
return
elseif #datas < 1 then
-- Материал не крафтится, это ОК
rule.parents = {}
rule.parents_n = 0
return
end
local data = datas[1]
local parents = {}
for _, data_in in pairs(data.inputs) do
if data_in.variants then
-- log:only((" Inputs variants: %s <- %s %s %s"):format(
-- rule.name, data_in.name, data_in.type, pretty.pretty(data_in)
-- ))
local variants = {}
for _, data_in_var in pairs(data_in.variants) do
local parent_rule = get_or_alloc_rule(data_in_var.name, data_in_var.type)
table.insert(queue, parent_rule)
table.insert(variants, data_in_var.name)
end
table.insert(parents, variants)
else
local parent_rule = get_or_alloc_rule(data_in.name, data_in.type)
table.insert(queue, parent_rule)
parents[data_in.name] = data_in.name
end
end
parents = kv2iv(parents)
rule.parents = parents
rule.parents_n = #parents
log:only("Inputs for ", rule.name)
log:only(" ", pretty.render(pretty.pretty(parents)))
end
local function build_rules_phase2_parent(queue, parent, try_scale, try_priority)
expect.expect(1, queue, "table")
expect.expect(2, parent, "table", "string", "nil")
expect.expect(3, try_scale, "number")
expect.expect(4, try_priority, "number")
local parent_type = type(parent)
if parent_type == "string" then
local rule = rules[parent]
local old_scale = rule.scale
local new_scale = rule.explicit_scale or math.max(old_scale or 0, try_scale)
if old_scale ~= new_scale then
rule.scale = new_scale
queue[rule.name] = true
end
local old_priority = rule.priority
local new_priority = rule.explicit_priority or math.max(old_priority or 0, try_priority)
if old_priority ~= new_priority then
rule.priority = new_priority
queue[rule.name] = true
end
elseif parent_type == "table" then
for _, sub_parent in pairs(parent) do
build_rules_phase2_parent(queue, sub_parent, try_scale, try_priority * 0.95)
end
else
error(("Invalid parent on phase 3: %s, %s"):format(parent_type, parent))
end
return changed
end
local function build_rules()
-- посроение дерева рецептов
local start_time = os.clock()
rules = {} -- сброс
log:print(("Rebuilding rules, phase 1... (Base rules: %d)"):format(count_pairs(config.rules)))
-- Фаза 1: создание записей
local queue1 = {}
build_rules_phase1_init(queue1)
local visited = {}
local i1, t1 = 0, os.clock()
while #queue1 > 0 do
i1 = i1 + 1
if i1 % 100 == 0 then sleep(0.001) end -- yield
local clock = os.clock()
if t1 + 3 < clock then
t1 = clock
log:print(("Phase 1... %d loops, %d rules"):format(i1, count_pairs(rules)))
end
local queue_entry = table.remove(queue1)
if not visited[queue_entry.name] then
build_rules_phase1_entry(queue1, queue_entry)
visited[queue_entry.name] = true
end
end
log:print(("Phase 1 done in %d loops. Phase 2..."):format(i1))
-- Фаза 2: обновление динамик параметров scale и priority
local queue2 = {}
rules_c = 0
for _, rule in pairs(rules) do
rules_c = rules_c + 1
queue2[rule.name] = true
end
local i2 = 0
while count_pairs(queue2) > 0 do
i2 = i2 + 1
if i2 % 100 == 0 then sleep(0.001) end -- yield
local queue_entry, _ = pop(queue2)
local rule = rules[queue_entry]
queue2[queue_entry] = nil
build_rules_phase2_parent(queue2, rule.parents, rule.scale, rule.priority or 0)
end
local work_time = os.clock() - start_time
log:print(("Phase 2 done in %d loops. Rebuilt %d rules, took %fs"):format(i2, rules_c, work_time))
end
local function select_rule_single(rule)
local has_job = jobs_by_object[rule.name] and true or false
if has_job or rule.parents_n < 1 or rule.scale < 0 then
-- Задача уже запущена, оно не крафтится или отключено
log:only(("Selector %s: job=%s, parents=%d, scale=%d, priority=%.1f"):format(
rule.name, has_job, rule.parents_n, rule.scale, rule.priority
))
return -math.huge, -math.huge
end
rule.object = all_objects[rule.name]
rule.current_amount = (rule.object and rule.object.amount) or 0
rule.parent_amount = min_of_parents(rule.parents)
if rule.type == "fluid" then
-- parent_amount всегда в формате вещей,
-- для корректного расчета необходимого mB приводим 9 вещей в 1B
rule.parent_amount = denormalize_fluid_amount(rule.parent_amount)
end
if rule.exact then
rule.goal_amount = rule.exact
rule.min = rule.exact
rule.max = rule.exact
else
rule.goal_amount = rule.parent_amount * rule.scale
if rule.min then rule.goal_amount = math.max(rule.goal_amount, rule.min) end
if rule.max then rule.goal_amount = math.min(rule.goal_amount, rule.max) end
end
if rule.current_amount > rule.goal_amount then
-- У нас уже есть нужное кол-во
log:only(("Selector %s: current=%d > goal=%d, scale=%.1f, priority=%.1f"):format(
rule.name, rule.current_amount, rule.goal_amount, rule.scale, rule.priority
))
return -math.huge, -math.huge
end
rule.need_amount = rule.goal_amount - rule.current_amount
rule.need_amount = math.max(math.floor(rule.need_amount), 1)
local weight = rule.need_amount * (rule.explicit_weight or 1)
rule.weight = weight
assert(rule.priority)
log:only(("Selector %s: current=%d + need=%d = goal=%d, scale=%.1f, priority=%.1f"):format(
rule.name, rule.current_amount, rule.need_amount, rule.goal_amount, rule.scale, rule.priority
))
return weight, rule.priority
end
local function select_rule()
-- выбрать самое неудовлетворенное правило
local sel_rule, sel_weight, sel_priority = nil, -math.huge, -math.huge
for _, rule in pairs(rules) do
local weight, priority = select_rule_single(rule)
if priority > sel_priority or (priority == sel_priority and weight > sel_weight) then
log:only(("Pick new rule: %s, w=%.1f, p=%.1f"):format(rule.name, weight, priority))
sel_rule, sel_weight, sel_priority = rule, weight, priority
end
end
return sel_rule, sel_weight, sel_priority
end
local function count_jobs()
return count_pairs(jobs_by_uuid), count_pairs(jobs_by_object)
end
local function add_job(object_name, job_uuid)
expect.expect(1, object_name, "string")
expect.expect(2, job_uuid, "string")
local c_b_uuid, c_b_object = count_jobs()
if object_name and job_uuid then
jobs_by_uuid[job_uuid] = object_name
jobs_by_object[object_name] = job_uuid
end
local c_a_uuid, c_a_object = count_jobs()
local message = ("Added job %s %s, %d %d -> %d %d"):format(job_uuid, object_name, c_b_uuid, c_b_object, c_a_uuid, c_a_object)
if c_a_uuid == c_a_object and c_b_uuid + 1 == c_a_uuid and c_b_object + 1 == c_a_object then
log:only(message)
else
log:error(message)
end
end
local function remove_job_uuid(job_uuid)
expect.expect(1, job_uuid, "string")
local c_b_uuid, c_b_object = count_jobs()
local object_name = jobs_by_uuid[job_uuid]
jobs_by_uuid[job_uuid] = nil
if object_name then
jobs_by_object[object_name] = nil
end
local c_a_uuid, c_a_object = count_jobs()
local message = ("Removed job %s %s, %d %d -> %d %d"):format(job_uuid, object_name, c_b_uuid, c_b_object, c_a_uuid, c_a_object)
if c_a_uuid == c_a_object and c_b_uuid == c_a_uuid + 1 and c_b_object == c_a_object + 1 then
log:only(message)
else
log:error(message)
end
end
local function sync_jobs()
sleep(0.001)
local c_b_uuid, c_b_object = count_jobs()
local issued_list = ae2cc.getIssuedCraftingJobs()
if #issued_list == 0 then
jobs_by_uuid = {}
jobs_by_object = {}
else
local issued_set = {}
for _, job in ipairs(issued_list) do
issued_set[job.jobID] = true
end
for job_uuid, object_name in pairs(jobs_by_uuid) do
if not issued_set[job_uuid] then
jobs_by_uuid[job_uuid] = nil
jobs_by_object[object_name] = nil
end
end
for object_name, job_uuid in pairs(jobs_by_object) do
if not issued_set[job_uuid] then
jobs_by_uuid[job_uuid] = nil
jobs_by_object[object_name] = nil
end
end
end
local c_a_uuid, c_a_object = count_jobs()
local message = ("Synced %d jobs: %d %d -> %d %d"):format(#issued_list, c_b_uuid, c_b_object, c_a_uuid, c_a_object)
if c_a_uuid == c_a_object and c_b_uuid >= c_a_uuid and c_b_object >= c_a_object then
log:only(message)
else
log:error(message)
end
return new_t
end
local function main_loop_inner()
-- term.clear()
-- term.setCursorPos(1, 1)
-- log:print(" ")
update_cpus()
if not can_craft then return nil end
update_objects()
local rule, rule_w, rule_p = select_rule()
if not rule then
log:print(("All %d rules satisfied or crafting!"):format(count_pairs(rules)))
-- Если правилла удовлетворены 10 раз подряд, то ливаем из цикла и перестраиваем правила.
-- Иначе считаем и ждем 5сек
if rules_satisfied > 10 then
return nil
else
rules_satisfied = rules_satisfied + 1
return 5
end
end
-- Но если какое-то правило не было удовлетварено, то сбрасываем счетчик.
rules_satisfied = 0
assert(rule.current_amount)
assert(rule.goal_amount)
assert(rule.need_amount)
assert(rule.scale)
assert(rule_w)
assert(rule_p)
local name_simple = simplify_ns(rule.name)
log:print(" ")
log:print(("Have %d, goal %d, need %d of %s (s=%.1f, w=%.1f p=%.1f). "):format(
rule.current_amount, rule.goal_amount, rule.need_amount, name_simple, rule.scale, rule_w, rule_p
))
local request_amount = rule.need_amount
request_amount = math.max(math.floor(request_amount * config.request_scale), 1)
-- Заказы не более request_limit
local request_limit = math.min(config.request_limit, (rule.request_limit or math.huge))
request_amount = math.min(request_amount, request_limit)
sleep(0.001) -- yield прежде чем таки запланируем задачу
if not jobs_by_object[rule.name] then
-- проверяем, вдруг ивенты нам что-то обновили по работам,
-- избегаем возмодных дубликатов работы
log:print(("Scheduling %d of %s..."):format(request_amount, name_simple))
local job_id = ae2cc.scheduleCrafting(rule.type, rule.name, request_amount)
add_job(rule.name, job_id)
log:print(("Result: %s"):format(job_id))
if request_amount == 1 then
return 0.001 -- без задержек, когда быстрые рецепты
else
return 1
end
else
return 5
end
end
local function main_loop_outer()
log:print(" ")
config = dofile("config.lua")
log:print("Reconnecting AE2...")
ae2cc = peripheral.find("ae2cc_adapter")
ae2upw = peripheral.find("ae2:energy_cell")
sleep(1)
update_cpus()
sync_jobs()
local c_b_uuid, c_b_object = count_jobs()
log:print(("CPUs: %d busy and %d free of %d, %d player's; Jobs: %d ~ %d"):format(
busy_cpus_c, free_cpus_c, all_cpus_c, busy_player_cpus_c, c_b_uuid, c_b_object
))
if not can_craft then
return math.max(10, busy_player_cpus_c * 10)
end
build_rules()
local loops = 0
while true do
loops = loops + 1
result = main_loop_inner()
if result then
sleep(result)
else
return 10 + math.max(0, 20 - loops)
end
end
end
local function main_loop()
while true do
-- local status, result = pcall(main_loop)
local status, result = true, main_loop_outer()
if status then
sleep(result)
else
log:error("Failure:", result)
sleep(10)
end
end
end
local function event_loop()
while true do
sleep(0.001)
local eventData = {os.pullEvent()}
local event = eventData[1]
if event == "ae2cc:crafting_started" then
local uuid = eventData[2]
local object_name = jobs_by_uuid[uuid]
log:print(("Job %s %s started!"):format(uuid, object_name))
elseif event == "ae2cc:crafting_cancelled" then
local uuid, reason = eventData[2], eventData[3]
local object_name = jobs_by_uuid[uuid]
log:error(("Job %s %s cancelled: %s"):format(uuid, object_name, reason))
os.sleep(1)
remove_job_uuid(uuid)
elseif event == "ae2cc:crafting_done" then
local uuid = eventData[2]
local object_name = jobs_by_uuid[uuid]
log:print(("Job %s %s done!"):format(uuid, object_name))
os.sleep(1)
remove_job_uuid(uuid)
end
end
end
log:print("Starting loops...")
parallel.waitForAll(event_loop, main_loop)
return {
default_scale = 0.5,
request_scale = 0.1,
request_limit = 64 * 8,
rules = {
["minecraft:paper"] = { priority = 500 },
["minecraft:magma_cream"] = { },
["minecraft:blaze_powder"] = { },
["minecraft:slime_ball"] = { priority = 500 },
["minecraft:birch_planks"] = { priority = 500 },
["minecraft:blue_ice"] = { },
-- -- --
["modern_industrialization:coal_crushed_dust"] = { exact = 0 },
["modern_industrialization:diamond_crushed_dust"] = { exact = 0 },
["modern_industrialization:emerald_crushed_dust"] = { exact = 0 },
["modern_industrialization:lapis_crushed_dust"] = { exact = 0 },
["modern_industrialization:redstone_crushed_dust"] = { exact = 0 },
["modern_industrialization:quartz_crushed_dust"] = { exact = 0 },
["modern_industrialization:ligite_coal_crushed_dust"] = { exact = 0 },
["modern_industrialization:bauxite_crushed_dust"] = { exact = 0 },
["modern_industrialization:salt_crushed_dust"] = { exact = 0 },
["modern_industrialization:chromium_crushed_dust"] = { exact = 0 },
["modern_industrialization:manganese_crushed_dust"] = { exact = 0 },
["modern_industrialization:monazite_crushed_dust"] = { exact = 0 },
-- -- --
["minecraft:white_dye"] = { exact = 1024, priority = 500 },
["minecraft:light_gray_dye"] = { exact = 1024, priority = 500 },
["minecraft:gray_dye"] = { exact = 1024, priority = 500 },
["minecraft:black_dye"] = { exact = 1024, priority = 500 },
["minecraft:brown_dye"] = { exact = 1024, priority = 200 },
["minecraft:red_dye"] = { exact = 1024, priority = 200 },
["minecraft:orange_dye"] = { exact = 1024, priority = 200 },
["minecraft:yellow_dye"] = { exact = 1024, priority = 200 },
["minecraft:lime_dye"] = { exact = 1024, priority = 200 },
["minecraft:green_dye"] = { exact = 1024, priority = 200 },
["minecraft:cyan_dye"] = { exact = 1024, priority = 200 },
["minecraft:light_blue_dye"] = { exact = 1024, priority = 200 },
["minecraft:blue_dye"] = { exact = 1024, priority = 200 },
["minecraft:purple_dye"] = { exact = 1024, priority = 200 },
["minecraft:magenta_dye"] = { exact = 1024, priority = 200 },
["minecraft:pink_dye"] = { exact = 1024, priority = 200 },
-- -- --
["modern_industrialization:lignite_coal_block"] = { priority = 2000 },
["minecraft:coal_block"] = { priority = 2000 },
["minecraft:iron_ingot"] = { },
["minecraft:gold_ingot"] = { },
["minecraft:copper_ingot"] = { },
["minecraft:redstone"] = { },
["minecraft:lapis_lazuli"] = { },
["minecraft:emerald"] = { },
["minecraft:diamond"] = { },
["minecraft:gravel"] = { },
["minecraft:sand"] = { priority = 500 },
["minecraft:glass"] = { },
["minecraft:tinted_glass"] = { },
-- -- --
["techreborn:chrome_dust"] = { scale = 0.05 },
["techreborn:titanium_dust"] = { scale = 0.05 },
["techreborn:platinum_dust"] = { scale = 0.05 },
-- -- --
["ae2:calculation_processor"] = { min = 64 },
["ae2:logic_processor"] = { min = 32 },
["ae2:engineering_processor"] = { min = 16 },
["ae2:quartz_fiber"] = { min = 64, priority = 200 },
["ae2:fluix_glass_cable"] = { min = 64, priority = 200 },
["ae2:me_chest"] = { min = 1 },
["ae2:cell_component_64k"] = { min = 1 },
["ae2:basic_card"] = { min = 32 },
["ae2:advanced_card"] = { min = 32 },
["megacells:accumulation_processor"] = { min = 8 },
-- -- --
["techreborn:steel_ingot"] = { },
["modern_industrialization:stainless_steel_ingot"] = { },
["modern_industrialization:turbo_machine_hull"] = { min = 8 },
["modern_industrialization:basic_upgrade"] = { min = 64 },
["modern_industrialization:advanced_upgrade"] = { min = 32 },
["modern_industrialization:turbo_upgrade"] = { min = 16 },
["modern_industrialization:highly_advanced_upgrade"] = { min = 8 },
["modern_industrialization:boosted_diesel"] = { type = "fluid", priority = 900, exact = 60 * 1000 },
["modern_industrialization:bronze_drill"] = { priority = 1000, exact = 8 },
["modern_industrialization:steel_drill"] = { priority = 990, exact = 8 },
["modern_industrialization:gold_drill"] = { priority = 980, exact = 8 },
["modern_industrialization:aluminum_drill"] = { priority = 970, exact = 8 },
["modern_industrialization:stainless_steel_drill"] = { priority = 960, exact = 8 },
["modern_industrialization:titanium_drill"] = { priority = 950, exact = 1 },
}
}
local expect = require "cc.expect"
local LogManager = {}
LogManager.__index = LogManager
function LogManager.new(baseName, maxFileSize, maxFiles)
local self = setmetatable({}, LogManager)
self.baseName = baseName
self.maxFileSize = maxFileSize
self.maxFiles = maxFiles
self.currentFile = 1
self:rotateFiles()
return self
end
function LogManager:rotateFiles()
if self.currentFileHandle then
self.currentFileHandle:close()
self.currentFileHandle = nil
end
for i = self.maxFiles, 1, -1 do
local oldName = self.baseName .. "." .. i .. ".log"
if i == self.maxFiles then
if fs.exists(oldName) then
fs.delete(oldName)
end
else
if fs.exists(oldName) then
local newName = self.baseName .. "." .. (i + 1) .. ".log"
fs.move(oldName, newName)
end
end
end
self.currentFileName = self.baseName .. ".1" .. ".log"
self.currentFileHandle = io.open(self.currentFileName, "w")
end
function LogManager:checkFileSize()
expect.field(self, "currentFileHandle", "table")
local currentSize = self.currentFileHandle:seek("end") or 0
assert(type(currentSize) == "number", type(currentSize) .. " " .. tostring(currentSize))
expect.field(self, "maxFileSize", "number")
if currentSize >= self.maxFileSize then
self:rotateFiles()
else
self.currentFileHandle:seek("set")
end
end
function LogManager:manualRotate()
self:rotateFiles()
end
function LogManager:log(message)
self:checkFileSize()
self.currentFileHandle:write("[" .. os.date("%y-%m-%d %H:%M:%S") .. "] " .. message .. "\n")
self.currentFileHandle:flush()
end
function LogManager:print(...)
local args = {...}
for i, v in ipairs(args) do
args[i] = tostring(v)
end
local message = table.concat(args, " ")
print(message)
self:log(message)
end
function LogManager:error(...)
local args = {...}
for i, v in ipairs(args) do
args[i] = tostring(v)
end
local message = table.concat(args, " ")
printError(message)
self:log(message)
end
function LogManager:only(...)
local args = {...}
for i, v in ipairs(args) do
args[i] = tostring(v)
end
local message = table.concat(args, " ")
self:log(message)
end
return LogManager
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment