diff --git a/[examples]/newmodels-example/meta.xml b/[examples]/newmodels-example/meta.xml
index f46610e..cd14d2a 100644
--- a/[examples]/newmodels-example/meta.xml
+++ b/[examples]/newmodels-example/meta.xml
@@ -3,12 +3,9 @@
Its version now matches the main newmodels version!
https://github.com/Fernando-A-Rocha/mta-add-models -->
-
-
-
+
+
+
diff --git a/[examples]/newmodels-example/server.lua b/[examples]/newmodels-example/server.lua
index cc14765..63fa76c 100644
--- a/[examples]/newmodels-example/server.lua
+++ b/[examples]/newmodels-example/server.lua
@@ -31,6 +31,8 @@ function (startedResource)
if not startUpChecks() then return end
+ local listToAdd = {}
+
for k,mod in pairs(myMods) do
local uid = mod[1]
@@ -48,17 +50,18 @@ function (startedResource)
if not baseid then
outputDebugString("Failed to get vehicle model from name: "..tostring(mod[2]), 0, 255,55,55)
else
- local worked, reason = exports.newmodels:addExternalMod_CustomFilenames(
- et, uid, baseid, name,
- dff, txd, col
- )
-
- if not worked then
- outputDebugString(reason or "Unknown error", 0, 255,110,61)
- end
+ -- ARGS: elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse
+ listToAdd[#listToAdd+1] = {et, uid, baseid, name, dff, txd, col, false, false, false, false}
end
end
+ local count, reason = exports.newmodels:addExternalMods_CustomFileNames(listToAdd)
+ if not count then
+ outputDebugString("[newmodels-example] Failed to add models: "..tostring(reason), 0, 255,110,61)
+ return
+ end
+
+
checkPossibleExistingElements()
end)
@@ -320,7 +323,7 @@ function testVehiclesCmd(thePlayer, cmd)
for elementType, mods in pairs(modList) do
if elementType == elementType2 then
for k,mod in pairs(mods) do
- local veh = createVehicle(400, x,y,z,rx,ry,rz)
+ local veh = createVehicle(mod.base_id, x,y,z,rx,ry,rz)
if veh then
setElementData(veh, data_name, mod.id)
table.insert(spawned_vehs, veh)
diff --git a/[examples]/sampobj_reloaded/meta.xml b/[examples]/sampobj_reloaded/meta.xml
index ef03b67..f8230b9 100644
--- a/[examples]/sampobj_reloaded/meta.xml
+++ b/[examples]/sampobj_reloaded/meta.xml
@@ -6,7 +6,7 @@
diff --git a/[examples]/unittest_newmodels/meta.xml b/[examples]/unittest_newmodels/meta.xml
index 0a65285..6f104e3 100644
--- a/[examples]/unittest_newmodels/meta.xml
+++ b/[examples]/unittest_newmodels/meta.xml
@@ -6,7 +6,7 @@
diff --git a/newmodels/_config.lua b/newmodels/_config.lua
index ef7139f..631effc 100644
--- a/newmodels/_config.lua
+++ b/newmodels/_config.lua
@@ -47,3 +47,12 @@ START_STOP_MESSAGES = true -- enable resouce start/stop automatic chat messages
SEE_ALLOCATED_TABLE = true -- automatically executes /allocatedids on startup
ENABLE_DEBUG_MESSAGES = true -- toggle all debug console messages
CHAT_DEBUG_MESSAGES = true -- make debug console messages to go chatbox (better readability imo)
+
+--[[
+ MTA:SA Async library Settings
+
+ Async:setPriority("low"); -- better fps
+ Async:setPriority("normal"); -- medium
+ Async:setPriority("high"); -- better perfomance
+]]
+ASYNC_PRIORITY = "normal"
diff --git a/newmodels/async_s.lua b/newmodels/async_s.lua
new file mode 100644
index 0000000..a3261aa
--- /dev/null
+++ b/newmodels/async_s.lua
@@ -0,0 +1,460 @@
+-- Used in https://github.com/Fernando-A-Rocha/mta-add-models
+-- https://github.com/inlife/mta-lua-async
+
+local function loadClass()
+ ---------
+ -- Start of slither.lua dependency
+ ---------
+
+ local _LICENSE = -- zlib / libpng
+ [[
+ Copyright (c) 2011-2014 Bart van Strien
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+ ]]
+
+ local class =
+ {
+ _VERSION = "Slither 20140904",
+ -- I have no better versioning scheme, deal with it
+ _DESCRIPTION = "Slither is a pythonic class library for lua",
+ _URL = "http://bitbucket.org/bartbes/slither",
+ _LICENSE = _LICENSE,
+ }
+
+ local function stringtotable(path)
+ local t = _G
+ local name
+
+ for part in path:gmatch("[^%.]+") do
+ t = name and t[name] or t
+ name = part
+ end
+
+ return t, name
+ end
+
+ local function class_generator(name, b, t)
+ local parents = {}
+ for _, v in ipairs(b) do
+ parents[v] = true
+ for _, v in ipairs(v.__parents__) do
+ parents[v] = true
+ end
+ end
+
+ local temp = { __parents__ = {} }
+ for i, v in pairs(parents) do
+ table.insert(temp.__parents__, i)
+ end
+
+ local class = setmetatable(temp, {
+ __index = function(self, key)
+ if key == "__class__" then return temp end
+ if key == "__name__" then return name end
+ if t[key] ~= nil then return t[key] end
+ for i, v in ipairs(b) do
+ if v[key] ~= nil then return v[key] end
+ end
+ if tostring(key):match("^__.+__$") then return end
+ if self.__getattr__ then
+ return self:__getattr__(key)
+ end
+ end,
+
+ __newindex = function(self, key, value)
+ t[key] = value
+ end,
+
+ allocate = function(instance)
+ local smt = getmetatable(temp)
+ local mt = {__index = smt.__index}
+
+ function mt:__newindex(key, value)
+ if self.__setattr__ then
+ return self:__setattr__(key, value)
+ else
+ return rawset(self, key, value)
+ end
+ end
+
+ if temp.__cmp__ then
+ if not smt.eq or not smt.lt then
+ function smt.eq(a, b)
+ return a.__cmp__(a, b) == 0
+ end
+ function smt.lt(a, b)
+ return a.__cmp__(a, b) < 0
+ end
+ end
+ mt.__eq = smt.eq
+ mt.__lt = smt.lt
+ end
+
+ for i, v in pairs{
+ __call__ = "__call", __len__ = "__len",
+ __add__ = "__add", __sub__ = "__sub",
+ __mul__ = "__mul", __div__ = "__div",
+ __mod__ = "__mod", __pow__ = "__pow",
+ __neg__ = "__unm", __concat__ = "__concat",
+ __str__ = "__tostring",
+ } do
+ if temp[i] then mt[v] = temp[i] end
+ end
+
+ return setmetatable(instance or {}, mt)
+ end,
+
+ __call = function(self, ...)
+ local instance = getmetatable(self).allocate()
+ if instance.__init__ then instance:__init__(...) end
+ return instance
+ end
+ })
+
+ for i, v in ipairs(t.__attributes__ or {}) do
+ class = v(class) or class
+ end
+
+ return class
+ end
+
+ local function inheritance_handler(set, name, ...)
+ local args = {...}
+
+ for i = 1, select("#", ...) do
+ if args[i] == nil then
+ error("nil passed to class, check the parents")
+ end
+ end
+
+ local t = nil
+ if #args == 1 and type(args[1]) == "table" and not args[1].__class__ then
+ t = args[1]
+ args = {}
+ end
+
+ for i, v in ipairs(args) do
+ if type(v) == "string" then
+ local t, name = stringtotable(v)
+ args[i] = t[name]
+ end
+ end
+
+ local func = function(t)
+ local class = class_generator(name, args, t)
+ if set then
+ local root_table, name = stringtotable(name)
+ root_table[name] = class
+ end
+ return class
+ end
+
+ if t then
+ return func(t)
+ else
+ return func
+ end
+ end
+
+ function class.private(name)
+ return function(...)
+ return inheritance_handler(false, name, ...)
+ end
+ end
+
+ class = setmetatable(class, {
+ __call = function(self, name)
+ return function(...)
+ return inheritance_handler(true, name, ...)
+ end
+ end,
+ })
+
+
+ function class.issubclass(class, parents)
+ if parents.__class__ then parents = {parents} end
+ for i, v in ipairs(parents) do
+ local found = true
+ if v ~= class then
+ found = false
+ for _, p in ipairs(class.__parents__) do
+ if v == p then
+ found = true
+ break
+ end
+ end
+ end
+ if not found then return false end
+ end
+ return true
+ end
+
+ function class.isinstance(obj, parents)
+ return type(obj) == "table" and obj.__class__ and class.issubclass(obj.__class__, parents)
+ end
+
+ -- Export a Class Commons interface
+ -- to allow interoperability between
+ -- class libraries.
+ -- See https://github.com/bartbes/Class-Commons
+ --
+ -- NOTE: Implicitly global, as per specification, unfortunately there's no nice
+ -- way to both provide this extra interface, and use locals.
+ if common_class ~= false then
+ common = {}
+ function common.class(name, prototype, superclass)
+ prototype.__init__ = prototype.init
+ return class_generator(name, {superclass}, prototype)
+ end
+
+ function common.instance(class, ...)
+ return class(...)
+ end
+ end
+
+ ---------
+ -- End of slither.lua dependency
+ ---------
+
+ return class;
+end
+
+local class = loadClass();
+
+--- GTA:MTA Lua async thread scheduler.
+-- @author Inlife
+-- @license MIT
+-- @url https://github.com/Inlife/mta-lua-async
+-- @dependency slither.lua https://bitbucket.org/bartbes/slither
+
+class "_Async" {
+
+ -- Constructor mehtod
+ -- Starts timer to manage scheduler
+ -- @access public
+ -- @usage local asyncmanager = async();
+ __init__ = function(self)
+
+ self.threads = {};
+ self.resting = 50; -- in ms (resting time)
+ self.maxtime = 200; -- in ms (max thread iteration time)
+ self.current = 0; -- starting frame (resting)
+ self.state = "suspended"; -- current scheduler executor state
+ self.debug = false;
+ self.priority = {
+ low = {500, 50}, -- better fps
+ normal = {200, 200}, -- medium
+ high = {50, 500} -- better perfomance
+ };
+
+ self:setPriority("normal");
+ end,
+
+
+ -- Switch scheduler state
+ -- @access private
+ -- @param boolean [istimer] Identifies whether or not
+ -- switcher was called from main loop
+ switch = function(self, istimer)
+ self.state = "running";
+
+ if (self.current + 1 <= #self.threads) then
+ self.current = self.current + 1;
+ self:execute(self.current);
+ else
+ self.current = 0;
+
+ if (#self.threads <= 0) then
+ self.state = "suspended";
+ return;
+ end
+
+ -- setTimer(function theFunction, int timeInterval, int timesToExecute)
+ -- (GTA:MTA server scripting function)
+ -- For other environments use alternatives.
+ setTimer(function()
+ self:switch();
+ end, self.resting, 1);
+ end
+ end,
+
+
+ -- Managing thread (resuming, removing)
+ -- In case of "dead" thread, removing, and skipping to the next (recursive)
+ -- @access private
+ -- @param int id Thread id (in table async.threads)
+ execute = function(self, id)
+ local thread = self.threads[id];
+
+ if (thread == nil or coroutine.status(thread) == "dead") then
+ table.remove(self.threads, id);
+ self:switch();
+ else
+ coroutine.resume(thread);
+ self:switch();
+ end
+ end,
+
+
+ -- Adding thread
+ -- @access private
+ -- @param function func Function to operate with
+ add = function(self, func)
+ local thread = coroutine.create(func);
+ table.insert(self.threads, thread);
+ end,
+
+
+ -- Set priority for executor
+ -- Use before you call 'iterate' or 'foreach'
+ -- @access public
+ -- @param string|int param1 "low"|"normal"|"high" or number to set 'resting' time
+ -- @param int|void param2 number to set 'maxtime' of thread
+ -- @usage async:setPriority("normal");
+ -- @usage async:setPriority(50, 200);
+ setPriority = function(self, param1, param2)
+ if (type(param1) == "string") then
+ if (self.priority[param1] ~= nil) then
+ self.resting = self.priority[param1][1];
+ self.maxtime = self.priority[param1][2];
+ end
+ else
+ self.resting = param1;
+ self.maxtime = param2;
+ end
+ end,
+
+ -- Set debug mode enabled/disabled
+ -- @access public
+ -- @param boolean value true - enabled, false - disabled
+ -- @usage async:setDebug(true);
+ setDebug = function(self, value)
+ self.debug = value;
+ end,
+
+
+ -- Iterate on interval (for cycle)
+ -- @access public
+ -- @param int from Iterate from
+ -- @param int to Iterate to
+ -- @param function func Iterate using func
+ -- Function func params:
+ -- @param int [i] Iteration index
+ -- @param function [callback] Callback function, called when execution finished
+ -- Usage:
+ -- @usage async:iterate(1, 10000, function(i)
+ -- outputDebugString(i);
+ -- end);
+ iterate = function(self, from, to, func, callback)
+ self:add(function()
+ local a = getTickCount();
+ local lastresume = getTickCount();
+ for i = from, to do
+ func(i);
+
+ -- int getTickCount()
+ -- (GTA:MTA server scripting function)
+ -- For other environments use alternatives.
+ if getTickCount() > lastresume + self.maxtime then
+ coroutine.yield()
+ lastresume = getTickCount()
+ end
+ end
+ if (self.debug) then
+ outputDebugString("[DEBUG]Async iterate: " .. (getTickCount() - a) .. "ms");
+ end
+ if (callback) then
+ callback();
+ end
+ end);
+
+ self:switch();
+ end,
+
+ -- Iterate over array (foreach cycle)
+ -- @access public
+ -- @param table array Input array
+ -- @param function func Iterate using func
+ -- Function func params:
+ -- @param int [v] Iteration value
+ -- @param int [k] Iteration key
+ -- @param function [callback] Callback function, called when execution finished
+ -- Usage:
+ -- @usage async:foreach(vehicles, function(vehicle, id)
+ -- outputDebugString(vehicle.title);
+ -- end);
+ foreach = function(self, array, func, callback)
+ self:add(function()
+ local a = getTickCount();
+ local lastresume = getTickCount();
+ for k,v in ipairs(array) do
+ func(v,k);
+
+ -- int getTickCount()
+ -- (GTA:MTA server scripting function)
+ -- For other environments use alternatives.
+ if getTickCount() > lastresume + self.maxtime then
+ coroutine.yield()
+ lastresume = getTickCount()
+ end
+ end
+ if (self.debug) then
+ outputDebugString("[DEBUG]Async foreach: " .. (getTickCount() - a) .. "ms");
+ end
+ if (callback) then
+ callback();
+ end
+ end);
+
+ self:switch();
+ end,
+}
+
+-- Async Singleton wrapper
+Async = {
+ instance = nil,
+};
+
+-- After first call, creates an instance and stores it
+local function getInstance()
+ if Async.instance == nil then
+ Async.instance = _Async();
+ end
+
+ return Async.instance;
+end
+
+-- proxy methods for public members
+function Async:setDebug(...)
+ getInstance():setDebug(...);
+end
+
+function Async:setPriority(...)
+ getInstance():setPriority(...);
+end
+
+function Async:iterate(...)
+ getInstance():iterate(...);
+end
+
+function Async:foreach(...)
+ getInstance():foreach(...);
+end
diff --git a/newmodels/meta.xml b/newmodels/meta.xml
index b5fb1e5..8a663c8 100644
--- a/newmodels/meta.xml
+++ b/newmodels/meta.xml
@@ -1,6 +1,6 @@
+ description="minimalistic library for adding new models to your server" version="2.0.2" type="script"/>
+
diff --git a/newmodels/server.lua b/newmodels/server.lua
index 63731d0..73e000b 100644
--- a/newmodels/server.lua
+++ b/newmodels/server.lua
@@ -215,8 +215,6 @@ function modCheckError(text)
end
function verifyOneMod(mod, elementType, used_ids)
- coroutine.yield()
-
-- 1. verify IDs
if not tonumber(mod.id) then
return modCheckError("Invalid mod ID '"..tostring(mod.id).."'")
@@ -311,16 +309,14 @@ function doModListChecks()
return modCheckError("Please remove mod from modList: player = {...}, it will be added automatically to match 'ped' mods")
end
- for k,mod in pairs(mods) do
-
- local co = coroutine.create(verifyOneMod)
- coroutine.resume(co, mod, elementType, used_ids)
-
- local _, result = coroutine.resume(co)
+ -- for _,mod in ipairs(mods) do
+ Async:foreach(mods, function(mod)
+ local result = verifyOneMod(mod, elementType, used_ids)
if not result then
return result
end
- end
+ -- end
+ end)
end
fixModList()
@@ -332,6 +328,8 @@ function (startedResource)
startTickCount = getTickCount()
+ Async:setPriority(ASYNC_PRIORITY)
+
if (STARTUP_VERIFICATIONS) then
-- STARTUP CHECKS
@@ -356,6 +354,9 @@ function (startedResource)
end
end
end
+
+ outputDebugString(resName.."' startup finished after "..(getTickCount() - startTickCount).."ms", 0, 255, 0, 255)
+ startTickCount = nil -- clear memory
end)
addEventHandler( "onResourceStop", resourceRoot,
@@ -448,7 +449,6 @@ function sendModListWhenReady(player)
return
end
- startTickCount = nil -- free memory
if isTimer(dontspamPlayers[player]) then killTimer(dontspamPlayers[player]) end
dontspamPlayers[player] = setTimer(function()
if isElement(player) then
@@ -510,7 +510,6 @@ function setCustomElementModel(element, et, id)
end
-
--[[
This function exists to avoid too many exports calls of the function below from
external resources to add mods from those
@@ -520,22 +519,24 @@ function addExternalMods_IDFilenames(list) -- [Exported]
if type(list) ~= "table" then
return false, "Missing/Invalid 'list' table passed: "..tostring(list)
end
+
local countWorked = 0
- for _, modInfo in ipairs(list) do
+
+ -- for _, modInfo in ipairs(list) do
+ Async:foreach(list, function(modInfo)
+
if type(modInfo) ~= "table" then
return false, "Missing/Invalid 'modInfo' table passed: "..tostring(modInfo)
end
- local elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse = unpack(modInfo)
- local co = coroutine.create(addExternalMod_IDFilenames)
- coroutine.resume(co, elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, true)
- local _, worked, reason = coroutine.resume(co)
+ local worked, reason = addExternalMod_IDFilenames(unpack(modInfo))
if worked then
countWorked = countWorked + 1
else
return false, "Aborting, one failed, reason: "..tostring(reason)
end
- end
+ -- end
+ end)
return countWorked
end
@@ -543,10 +544,7 @@ end
The difference between this function and addExternalMod_CustomFilenames is that
you pass a folder path in 'path' and it will search for ID.dff ID.txd etc
]]
-function addExternalMod_IDFilenames(elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, usingCoroutine) -- [Exported]
- if (usingCoroutine == true) then
- coroutine.yield()
- end
+function addExternalMod_IDFilenames(elementType, id, base_id, name, path, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse) -- [Exported]
local sourceResName = getResourceName(sourceResource)
if sourceResName == resName then
@@ -661,22 +659,23 @@ function addExternalMods_CustomFileNames(list) -- [Exported]
if type(list) ~= "table" then
return false, "Missing/Invalid 'list' table passed: "..tostring(list)
end
+
local countWorked = 0
- for _, modInfo in ipairs(list) do
+
+ -- for _, modInfo in ipairs(list) do
+ Async:foreach(list, function(modInfo)
+
if type(modInfo) ~= "table" then
return false, "Missing/Invalid 'modInfo' table passed: "..tostring(modInfo)
end
- local elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse = unpack(modInfo)
-
- local co = coroutine.create(addExternalMod_CustomFilenames)
- coroutine.resume(co, elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, true)
- local _, worked, reason = coroutine.resume(co)
+ local worked, reason = addExternalMod_CustomFilenames(unpack(modInfo))
if worked then
countWorked = countWorked + 1
else
return false, "Aborting, one failed, reason: "..tostring(reason)
end
- end
+ -- end
+ end)
return countWorked
end
@@ -684,10 +683,7 @@ end
The difference between this function and addExternalMod_IDFilenames is that
you pass directly individual file paths for dff, txd and col files
]]
-function addExternalMod_CustomFilenames(elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse, usingCoroutine) -- [Exported]
- if (usingCoroutine == true) then
- coroutine.yield()
- end
+function addExternalMod_CustomFilenames(elementType, id, base_id, name, path_dff, path_txd, path_col, ignoreTXD, ignoreDFF, ignoreCOL, metaDownloadFalse) -- [Exported]
local sourceResName = getResourceName(sourceResource)
if sourceResName == resName then
@@ -836,28 +832,33 @@ function removeExternalMod(id) -- [Exported]
for elementType,mods in pairs(modList) do
for k,mod in pairs(mods) do
if mod.id == id then
+ local sourceResName = mod.srcRes
+ if sourceResName then
- outputDebugString("Removed "..elementType.." mod ID "..id, 0, 211, 255, 89)
-
- modList[elementType][k] = nil
+ outputDebugString("Removed "..elementType.." mod ID "..id, 0, 211, 255, 89)
- fixModList()
- sendModListWhenReady_ToAllPlayers()
-
- -- Don't spam chat/debug when mass adding/removing mods
- if isTimer(prevent_addrem_spam.remtimer) then killTimer(prevent_addrem_spam.remtimer) end
-
- if not prevent_addrem_spam.rem[sourceResName] then prevent_addrem_spam.rem[sourceResName] = {} end
- table.insert(prevent_addrem_spam.rem[sourceResName], true)
-
- prevent_addrem_spam.remtimer = setTimer(function()
- for rname,mods2 in pairs(prevent_addrem_spam.rem) do
- outputDebugString("Removed "..#mods2.." mods from "..rname, 0, 211, 255, 89)
- prevent_addrem_spam.rem[rname] = nil
- end
- end, 1000, 1)
-
- return true
+ modList[elementType][k] = nil
+
+ fixModList()
+ sendModListWhenReady_ToAllPlayers()
+
+ -- Don't spam chat/debug when mass adding/removing mods
+ if isTimer(prevent_addrem_spam.remtimer) then killTimer(prevent_addrem_spam.remtimer) end
+
+ if not prevent_addrem_spam.rem[sourceResName] then prevent_addrem_spam.rem[sourceResName] = {} end
+ table.insert(prevent_addrem_spam.rem[sourceResName], true)
+
+ prevent_addrem_spam.remtimer = setTimer(function()
+ for rname,mods2 in pairs(prevent_addrem_spam.rem) do
+ outputDebugString("Removed "..#mods2.." mods from "..rname, 0, 211, 255, 89)
+ prevent_addrem_spam.rem[rname] = nil
+ end
+ end, 1000, 1)
+
+ return true
+ else
+ return false, "Mod with ID "..id.." doesn't have a source resource"
+ end
end
end
end