--- @section Utils Module --- Utils module contains generic utility functions. It must be dynamically loaded before use: --- ```lua --- local utils = require "utils" --- ``` local utils = {} --- Checks whether a table contains a given item. Assumes the table is an array! function utils.contains(t, item) for i = 1, #t do if t[i] == item then return true end end return false end --- Returns the index of an item in a table, or nil if the item could not be found. Assumes the table is an array! function utils.index_of(t, item) for i = 1, #t do if t[i] == item then return i end end return nil end --- Counts how many times an item is contained in an array. function utils.count(t, item) local cnt = 0 for i = 1, #t do if t[i] == item then cnt = cnt + 1 end end return cnt end --- Removes element at given index from table by swapping it the last element. --- This is much faster than table.remove but changes the order of elements in the table. function utils.remove_unordered(t, index) t[index] = t[#t] t[#t] = nil end --- Returns a random element from a table. function utils.random_element(t) return t[math.random(1, #t)] end --- Shuffles the elements of a table in random order. function utils.shuffle(t) for i = 1, #t do local j = math.random(1, #t) t[i], t[j] = t[j], t[i] end end --- Returns the number of key-value pairs in a table. function utils.num_pairs(t) local count = 0 for _, _ in t do count = count + 1 end return count end --- Creates a shallow copy of a table. function utils.copy(t) local copy = {} for k,v in t do copy[k] = v end return copy end --- Returns an array containing all the keys of a table. function utils.keys(t) local keys = {} for k, _ in t do keys[#keys + 1] = k end return keys end --- Returns an array containing all the values of a table. function utils.values(t) local values = {} for _, v in t do values[#values + 1] = v end return values end --- Creates a set from an array. --- For example, utils.make_set{ 1, 2, 3 } returns a table with keys 1, 2 and 3 set to true. function utils.make_set(array) local s = {} for _, value in array do s[value] = true end return s end --- Removes and returns the last element from an array/set. function utils.pop(t) local item = t[#t] t[#t] = nil return item end --- Shallow merges the contents of t2 into t1. function utils.merge(t1, t2) for key, value in t2 do t1[key] = value end end --- Addends the contents of array t2 into array t1. function utils.append(t1, t2) local i = #t1 for j = 1, #t2 do i = i + 1 t1[i] = t2[j] end end local function dump_internal(t, depth, dumped, result) if type(t) == "string" then table.insert(result, string.format("%q", t)) elseif type(t) ~= "table" then table.insert(result, tostring(t)) else -- call tostring metamethod on tables if available if type(t) == "table" and getmetatable(t) and getmetatable(t).__tostring then table.insert(result, tostring(t)) return end -- detect circular references if dumped[t] then table.insert(result, tostring(t)) return end dumped[t] = true table.insert(result, "{\n") depth = depth + 1 -- print array part first -- can't use ipairs here because it calls metamethods and we want raw dumping local maxn = 0 for i = 1, #t do local v = t[i] if v == nil then break end table.insert(result, string.rep("\t", depth)) dump_internal(v, depth, dumped, result) table.insert(result, ",\n") maxn = math.max(maxn, i) end -- print hash part for k, v in t do local is_array_key = type(k) == "number" and k >= 1 and k <= maxn and math.floor(k) == k if not is_array_key then table.insert(result, string.rep("\t", depth)) local is_identifier = type(k) == "string" and string.match(k, "[%a_][%w_]*") if is_identifier then table.insert(result, k) table.insert(result, " = ") else table.insert(result, "[") if type(k) == "string" then table.insert(result, string.format("%q", k)) else table.insert(result, tostring(k)) end table.insert(result, "] = ") end dump_internal(v, depth, dumped, result) table.insert(result, ",\n") end end depth = depth - 1 table.insert(result, string.rep("\t", depth)) table.insert(result, "}") end end --- Pretty prints any Lua value to a string and returns the pretty printed value. function utils.dump_to_string(t) local result = {} dump_internal(t, 0, {}, result) return table.concat(result) end --- Pretty prints any Lua value. function utils.dump(t) print(utils.dump_to_string(t)) end -- To test utils.dump, paste this to any Lua script: -- local t = { 1, 2, "abc", nil, self, [1.5] = "float", ["abc"] = 1, nested = { "zzz", false, { 1, 2, vector(4.0, 5.0, 6.0) } }, [vector(0.0, 0.0)] = "bar", [function() end] = function() end, [{}] = "table" } -- t.loop = t -- utils.dump(t) --- Runs a function and prints its execution time. For example "test.lua:10: 1.23ms". --- 'name' is optional. If given, filename and line is replaced with value of 'name' in the output. function utils.bench(name, func) if func == nil then local func_name, line = debug.info(2, "nl") if func_name == "" then func_name = "" end func = name name = string.format("%s:%s", func_name, line) end local start_time = nl_clock() func() local time = nl_clock() - start_time print(string.format("%s: %.2fms", name, time * 1000.0)) end --- Copies the sign of number 'y' to number 'x' and returns it. For example, utils.copy_sign(2.5, -0.1) returns -2.5. function utils.copy_sign(x, y) x = math.abs(x) if y >= 0.0 then return x else return -x end end --- Returns true if 'x' is NaN (undefined or unpresentable floating-point value). function utils.is_nan(x) return x ~= x end --- Returns true if 'x' is infinite. function utils.is_inf(x) return not (x > -math.huge and x < math.huge) end --- Returns true if 'x' is an integer number. function utils.is_integer(x) return type(x) == "number" and math.floor(x) == x end --- Returns true if 'x' is a power of two. function utils.is_pow2(x) return x ~= 0 and bit32.band(x, (x - 1)) == 0 end --- Rounds 'x' up to next power of two. function utils.next_pow2(x) local r = 1 while r < x do r = r * 2 end return r end --- Rounds 'x' up to nearest multiple of 'alignment'. For example, utils.align(503, 512) returns 512. function utils.align(x, alignment) return math.floor((x + (alignment - 1)) / alignment) * alignment end --- Interpolates from 0.0 to 1.0 smoothly (using cubic interpolation) as 't' goes from 'min' to 'max'. --- 'min' and 'max' are optional and default to 0.0 and 1.0. --- DEPRECATED: Use nl_smoothstep instead. function utils.smoothstep(t, min, max) min = min or 0.0 max = max or 1.0 t = math.clamp((t - min) / (max - min), 0.0, 1.0) return t * t * (3.0 - 2.0 * t) end --- Like smoothstep() but the interpolation is even smoother. --- DEPRECATED: Use nl_smootherstep instead. function utils.smootherstep(t, min, max) min = min or 0.0 max = max or 1.0 t = math.clamp((t - min) / (max - min), 0.0, 1.0) return t * t * t * (t * (t * 6.0 - 15.0) + 10.0) end --- Linearly interpolates 'a' to 'b' as 't' goes from 0.0 to 1.0. --- DEPRECATED: Use nl_lerp (for numbers) or lerp2/3/4 (for vectors) instead. function utils.lerp(a, b, t) return a * (1.0 - t) + b * t end --- Linearly interpolates 'x' towards 'target' by 'amount', so that the value does not go past 'target'. function utils.lerp_towards(x, target, amount) if x < target then x = math.min(x + amount, target) elseif x > target then x = math.max(x - amount, target) end return x end --- Remap a value from one range to another. function utils.remap(value, from, to, from2, to2) return (value - from) / (to - from) * (to2 - from2) + from2 end --- Splits a 32-bit color value to red, green, blue and alpha components. function utils.split_color(color) local r = bit32.band(bit32.rshift(color, 24), 0xff) local g = bit32.band(bit32.rshift(color, 16), 0xff) local b = bit32.band(bit32.rshift(color, 8), 0xff) local a = bit32.band(color, 0xff) return r, g, b, a end --- Pack red, green, blue and alpha components to a 32-bit color value. function utils.pack_color(r, g, b, a) return bit32.bor(bit32.lshift(r, 24), bit32.lshift(g, 16), bit32.lshift(b, 8), a) end --- Linearly interpolates two 32-bit color values. function utils.lerp_color(color1, color2, t) local r1, g1, b1, a1 = utils.split_color(color1) local r2, g2, b2, a2 = utils.split_color(color2) local r = math.floor(r1 * (1.0 - t) + r2 * t) local g = math.floor(g1 * (1.0 - t) + g2 * t) local b = math.floor(b1 * (1.0 - t) + b2 * t) local a = math.floor(a1 * (1.0 - t) + a2 * t) return utils.pack_color(r, g, b, a) end --- Multiplies two 32-bit color values together. function utils.multiply_colors(color1, color2) local r1, g1, b1, a1 = utils.split_color(color1) local r2, g2, b2, a2 = utils.split_color(color2) local r = math.floor(r1 * r2 / 255.0) local g = math.floor(g1 * g2 / 255.0) local b = math.floor(b1 * b2 / 255.0) local a = math.floor(a1 * a2 / 255.0) return utils.pack_color(r, g, b, a) end --- Returns 'a' if 'cond' is true, otherwise returns 'b'. function utils.select(cond, a, b) -- TODO: remove this! Luau supports if expressions if cond then return a else return b end end --- Finds occurrence of 'what' inside 'str' without pattern matching. --- Same as calling string.find() with pattern matching disabled. --- 'start_index' is optional (default 1). function utils.find_plain(str, what, start_index) return string.find(str, what, start_index or 1, true) end -- Counts how many times the substring 'what' is contained in 'str'. -- By default 'what' is a Lua string pattern, but pattern matching can be turned off by setting 'plain' to true. function utils.str_count(str, what, plain) if #what == 0 then return 0 end local cnt = 0 local i = 1 while true do local n = string.find(str, what, i, plain) if n == nil then return cnt end cnt = cnt + 1 i = n + 1 end end --- Returns the string with first character in upper case. For example, utils.capitalize("test") returns "Test". function utils.capitalize(str) return string.upper(str:sub(1, 1)) .. str:sub(2) end --- Converts a string in snake case to title case (e.g. "foo_bar" -> "Foo Bar") function utils.snake_to_title_case(str) str = string.gsub(str, "(.-)_(%a)(.-)", function(s1, s2, s3) return s1 .. " " .. string.upper(s2) .. s3 end) str = string.gsub(str, "^(%l)(.*)", function(s1, s2) return string.upper(s1) .. s2 end) return str end --- Returns given text word-wrapped to a paragraph of given width by inserting linebreaks when a line would get too long. function utils.word_wrap(text, max_length) local result = "" local start = 1 local text_len = #text for i = 1, text_len do local ch = string.byte(text, i) local line_break if ch == 32 then -- space if i - start >= max_length then line_break = i - 1 end elseif ch == 10 then -- line break line_break = i - 1 elseif i == text_len then -- end of text line_break = i end if line_break then if #result > 0 then result = result .. "\n" end result = result .. text:sub(start, line_break) start = i + 1 end end -- append any leftover text, in case the input text ends with a space if start < text_len then result = result .. text:sub(start) end return result end return utils