diff --git a/data/expression2/tests/runtime/base/lambdas.txt b/data/expression2/tests/runtime/base/lambdas.txt index 8e35132c92..0b70e3341c 100644 --- a/data/expression2/tests/runtime/base/lambdas.txt +++ b/data/expression2/tests/runtime/base/lambdas.txt @@ -84,4 +84,4 @@ function wrapper() { } wrapper() -assert(Ran) \ No newline at end of file +assert(Ran) diff --git a/lua/entities/gmod_wire_expression2/base/compiler.lua b/lua/entities/gmod_wire_expression2/base/compiler.lua index 8b4c8f9afa..3db0047990 100644 --- a/lua/entities/gmod_wire_expression2/base/compiler.lua +++ b/lua/entities/gmod_wire_expression2/base/compiler.lua @@ -1440,6 +1440,10 @@ local CompileVisitors = { function(args) local s_scopes, s_scope, s_scopeid = state.Scopes, state.Scope, state.ScopeID + -- Not using `self.scope.data.ops` in order to add prf when builtin functions call lambdas. + -- This behavior may change. + state.prf = state.prf + 10 + local scope = { vclk = {} } state.Scopes = inherited_scopes state.ScopeID = after @@ -1872,8 +1876,6 @@ local CompileVisitors = { end end, ret_type elseif expr_ty == "f" then - self.scope.data.ops = self.scope.data.ops + 15 -- Since functions are 10 ops, this is pretty lenient. I will decrease this slightly when functions are made static and cheaper. - local nargs = #args local sig = table.concat(arg_types) diff --git a/lua/entities/gmod_wire_expression2/core/e2lib.lua b/lua/entities/gmod_wire_expression2/core/e2lib.lua index 1dc22b8fc1..e81b2c586c 100644 --- a/lua/entities/gmod_wire_expression2/core/e2lib.lua +++ b/lua/entities/gmod_wire_expression2/core/e2lib.lua @@ -87,20 +87,13 @@ end E2Lib.Lambda = Function ---- Call the function without doing any type checking. ---- Only use this when you check self:Args() yourself to ensure you have the correct signature function. +--- Call the function without doing any type checking, and outside of the proper entity context. +--- **ONLY USE THIS IF YOU KNOW WHAT YOU'RE DOING.** This could hit perf without actually erroring the chip. +--- Also make sure to check self:Args() yourself to ensure you have the correct signature function. function Function:UnsafeCall(args) return self.fn(args) end -function Function:Call(args, types) - if self.arg_sig == types then - return self.fn(args) - else - error("Incorrect arguments passed to lambda") - end -end - function Function:Args() return self.arg_sig end @@ -109,7 +102,7 @@ function Function:Ret() return self.ret end ---- If given the correct arguments, returns the inner untyped function you can call. +--- If given the correct arguments, returns the inner untyped function you can then call with ENT:Execute(f). --- Otherwise, throws an error to the given E2 Context. ---@param arg_sig string ---@param ctx RuntimeContext diff --git a/lua/entities/gmod_wire_expression2/core/timer.lua b/lua/entities/gmod_wire_expression2/core/timer.lua index a2c1b97550..d2d82cf9bf 100644 --- a/lua/entities/gmod_wire_expression2/core/timer.lua +++ b/lua/entities/gmod_wire_expression2/core/timer.lua @@ -1,121 +1,174 @@ -/******************************************************************************\ - Timer support -\******************************************************************************/ +--[[ + Timers +]] -local timerid = 0 +---@type table, count: integer }> +local Timers = {} -local function Execute(self, name) - self.data.timer.runner = name +--- Max timers that can exist at one time per chip. +local MAX_TIMERS = CreateConVar("wire_expression2_timer_max", 100) - self.data['timer'].timers[name] = nil +local function addTimer(self, name, delay, reps, callback) + local timers = Timers[self] + if not timers.lookup[name] then + timers.lookup[name] = true + timers.count = timers.count + 1 - if(self.entity and self.entity.Execute) then - self.entity:Execute() - end - - if !self.data['timer'].timers[name] then - timer.Remove("e2_" .. self.data['timer'].timerid .. "_" .. name) + if timers.count > MAX_TIMERS:GetInt() then + return self:throw("Hit per-chip timer limit of " .. MAX_TIMERS:GetInt() .. "!", nil) + end end - self.data.timer.runner = nil + timer.Create(("e2timer_%p_%s"):format(self, name), math.max(delay, 1e-2), reps, callback) end -local function AddTimer(self, name, delay) - if delay < 10 then delay = 10 end +local function removeTimer(self, name) + local timers = Timers[self] + if timers.lookup[name] then + timers.lookup[name] = nil + timers.count = timers.count - 1 - local timerName = "e2_" .. self.data.timer.timerid .. "_" .. name - - if self.data.timer.runner == name and timer.Exists(timerName) then - timer.Adjust(timerName, delay / 1000, 2, function() - Execute(self, name) - end) - timer.Start(timerName) - elseif !self.data['timer'].timers[name] then - timer.Create(timerName, delay / 1000, 2, function() - Execute(self, name) - end) + timer.Remove(("e2timer_%p_%s"):format(self, name)) end - - self.data['timer'].timers[name] = true end -local function RemoveTimer(self, name) - if self.data['timer'].timers[name] then - timer.Remove("e2_" .. self.data['timer'].timerid .. "_" .. name) - self.data['timer'].timers[name] = nil - end -end - -/******************************************************************************/ - registerCallback("construct", function(self) - self.data['timer'] = {} - self.data['timer'].timerid = timerid - self.data['timer'].timers = {} - - timerid = timerid + 1 + Timers[self] = { lookup = {}, count = 0 } end) registerCallback("destruct", function(self) - for name,_ in pairs(self.data['timer'].timers) do - RemoveTimer(self, name) + for name in pairs(Timers[self].lookup) do + removeTimer(self, name) end + + Timers[self] = nil end) -/******************************************************************************/ +__e2setcost(25) -__e2setcost(20) +---@param self RuntimeContext +local function MAKE_TRIGGER(id, self) + return function() + self.data.timer = id + Timers[self].lookup[id] = nil + + if self.entity and self.entity.Execute then + self.entity:Execute() + end + + if + Timers[self] -- This case is needed if chip tick quotas, which would call destruct hook on :Execute(). + and not Timers[self].lookup[id] + then + removeTimer(self, id) -- only remove if not immediately re-created + end + + self.data.timer = nil + end +end + +[deprecated = "Use the timer function with callbacks instead"] e2function void interval(rv1) - AddTimer(self, "interval", rv1) + addTimer(self, "interval", rv1 / 1000, 1, MAKE_TRIGGER("interval", self)) end +[deprecated = "Use the timer function with callbacks instead"] e2function void timer(string rv1, rv2) - AddTimer(self, rv1, rv2) + addTimer(self, rv1, rv2 / 1000, 1, MAKE_TRIGGER(rv1, self)) end __e2setcost(5) e2function void stoptimer(string rv1) - RemoveTimer(self, rv1) + removeTimer(self, rv1) end __e2setcost(1) -[nodiscard] +[nodiscard, deprecated = "Use the timer function with callbacks instead"] e2function number clk() - return self.data.timer.runner == "interval" and 1 or 0 + return self.data.timer == "interval" and 1 or 0 end -[nodiscard] +[nodiscard, deprecated = "Use the timer function with callbacks instead"] e2function number clk(string rv1) - return self.data.timer.runner == rv1 and 1 or 0 + return self.data.timer == rv1 and 1 or 0 end -[nodiscard] +[nodiscard, deprecated = "Use the timer function with callbacks instead"] e2function string clkName() - return self.data.timer.runner or "" + return self.data.timer or "" end +__e2setcost(5) + +[nodiscard, deprecated = "You should keep track of timers with callbacks instead"] e2function array getTimers() - local ret = {} + local ret, timers = {}, Timers[self] + self.prf = self.prf + timers.count * 2 + local i = 0 - for name in pairs( self.data.timer.timers ) do + for name in pairs(timers.lookup) do i = i + 1 ret[i] = name end - self.prf = self.prf + i * 5 + return ret end e2function void stopAllTimers() - for name in pairs(self.data.timer.timers) do - self.prf = self.prf + 5 - RemoveTimer(self,name) + local timers = Timers[self] + self.prf = self.prf + timers.count * 2 + + for name in pairs(timers.lookup) do + removeTimer(self, name) end end -/******************************************************************************/ +--[[ + Timers 2.0 +]] + +__e2setcost(15) + +local simpletimer = 1 + +e2function void timer(number delay, function callback) + local fn, ent = callback:Unwrap("", self), self.entity + + simpletimer = (simpletimer + 1) % (MAX_TIMERS:GetInt() * 100000000) -- if this ends up overwriting other timers you have a much bigger problem. wrap to avoid inf. + local name = tostring(simpletimer) + + addTimer(self, name, delay, 1, function() + removeTimer(self, name) + ent:Execute(fn) + end) +end + +e2function void timer(string name, number delay, function callback) + local fn, ent = callback:Unwrap("", self), self.entity + addTimer(self, name, delay, 1, function() + removeTimer(self, name) + ent:Execute(fn) + end) +end + +e2function void timer(string name, number delay, number reps, function callback) + local fn, ent, rep = callback:Unwrap("", self), self.entity, 0 + addTimer(self, name, delay, reps, function() + rep = rep + 1 + if rep == reps then + removeTimer(self, name) + end + + ent:Execute(fn) + end) +end + +--[[ + Time Monitoring +]] [nodiscard] e2function number curtime() @@ -132,7 +185,9 @@ e2function number systime() return SysTime() end ------------------------------------------------------------------------------------ +--[[ + Datetime +]] local function luaDateToE2Table( time, utc ) local ret = E2Lib.newE2Table() @@ -170,13 +225,14 @@ e2function table dateUTC() return luaDateToE2Table(nil,true) end --- Returns the specified time formatted neatly in a table using UTC +[nodiscard] e2function table dateUTC( time ) return luaDateToE2Table(time,true) end -- This function has a strange and slightly misleading name, but changing it might break older E2s, so I'm leaving it -- It's essentially the same as the date function above +[nodiscard] e2function number time(string component) local ostime = os.date("!*t") local ret = ostime[component] @@ -188,7 +244,7 @@ end ----------------------------------------------------------------------------------- __e2setcost(2) --- Returns the time in seconds + [nodiscard] e2function number time() return os.time() diff --git a/lua/entities/gmod_wire_expression2/init.lua b/lua/entities/gmod_wire_expression2/init.lua index ac28b37ba4..56b3339219 100644 --- a/lua/entities/gmod_wire_expression2/init.lua +++ b/lua/entities/gmod_wire_expression2/init.lua @@ -111,9 +111,12 @@ function ENT:UpdatePerf() end -function ENT:Execute() +function ENT:Execute(script, args) if self.error or not self.context or self.context.resetting then return end + script = script or self.script + args = args or self.context + self:PCallHook('preexecute') self.context.stackdepth = self.context.stackdepth + 1 @@ -124,7 +127,7 @@ function ENT:Execute() local bench = SysTime() - local ok, msg = pcall(self.script, self.context) + local ok, msg = pcall(script, args) if not ok then local _catchable, msg, trace = E2Lib.unpackException(msg) @@ -180,6 +183,8 @@ function ENT:Execute() if self.error then self:Destruct() end + + return msg end ---@param evt string diff --git a/lua/wire/client/e2descriptions.lua b/lua/wire/client/e2descriptions.lua index 46b59838b4..783e641a45 100644 --- a/lua/wire/client/e2descriptions.lua +++ b/lua/wire/client/e2descriptions.lua @@ -944,9 +944,15 @@ E2Helper.Descriptions["getTimers()"] = "Returns an array of all timers used in t E2Helper.Descriptions["interval(n)"] = "Sets a one-time timer with name \"interval\" and delay in milliseconds (minimum delay for timers is 10ms)" E2Helper.Descriptions["runOnTick(n)"] = "If set to 1, the expression will execute once every game tick" E2Helper.Descriptions["timer(sn)"] = "Sets a one-time timer with entered name and delay in milliseconds" + E2Helper.Descriptions["stoptimer(s)"] = "Stops a timer, can stop interval with stoptimer(\"interval\")" E2Helper.Descriptions["stopAllTimers()"] = "Stops all timers" +-- Timers 2.0 +E2Helper.Descriptions["timer(nf)"] = "Sets a callback to run after n seconds" +E2Helper.Descriptions["timer(snf)"] = "Sets a named timer to run a callback after n seconds" +E2Helper.Descriptions["timer(snnf)"] = "Sets a named timer to run a callback after n seconds, repeating n2 times" + -- Unit conversion E2Helper.Descriptions["toUnit(sn)"] = "Converts default garrysmod units to specified units" E2Helper.Descriptions["fromUnit(sn)"] = "Converts specified units to default garrysmod units"