Maris Posted January 24, 2015 Share Posted January 24, 2015 Case 1:local prefab = "evergreen" --could be anything elselocal interval = 1 --secAddPrefabPostInit(prefab,function(inst) inst:DoPeriodicTask(interval,function(inst) --Do some staff with high CPU load end)end)And case 2:local prefab = "evergreen" --could be anything elselocal interval = 1 --secAddPrefabPostInit(prefab,function(inst) inst:AddTag("some_tag")end)AddPrefabPostInit("forest",function(inst) inst:DoPeriodicTask(interval,function(inst) local ents = GLOBAL.TheSim:FindEntities(0,0,0,1200,{"some_tag"}) for i,v in ipairs(ents) do --Do some staff with high CPU load end end)end)What is better? Link to comment Share on other sites More sharing options...
rezecib Posted January 24, 2015 Share Posted January 24, 2015 @Maris, Both are pretty inefficient, I would say, but the first is better-- FindEntities is pretty CPU-expensive, I think. Instead, what you want to do is have a periodic task on the world, that refers to a list of entities. Have AddPrefabPostInit on the entities to add them to the list (and make sure you remove them with the onremove). For efficiency reason, you probably want to make the list have the entities as keys, and the value can be whatever you want-- some lists just use the entity again as the value. Link to comment Share on other sites More sharing options...
Maris Posted January 24, 2015 Author Share Posted January 24, 2015 I want to check each prefab each second.So you think I need an array and prefabs should update it on creation and deletion? Link to comment Share on other sites More sharing options...
Kzisor Posted January 24, 2015 Share Posted January 24, 2015 @Maris, checking every prefab every second will cause massive CPU consumption and performance issues no matter what you are doing. It's a bad practice and shouldn't be used ever. Instead look for a common factor in what you are wanting to do and push an event which your prefab is listening for to do what you want to do. This will ensure that it 1) only runs when you want it to run, 2) causes less CPU performance related issues and 3) is the best practice to use when programming. You can even listen for an event and push another event, however, that is technically bad practice as well if you are only listening and pushing a different event. You could easily just listen for the event and do what you are wanting to do without the overhead from the additional code. ~Kzisor/Ysovuka Link to comment Share on other sites More sharing options...
Maris Posted January 24, 2015 Author Share Posted January 24, 2015 I want to create garbage collector. So it should check every prefab and is there any players near prefab and is there any prefabs of the same type around. Link to comment Share on other sites More sharing options...
Kzisor Posted January 24, 2015 Share Posted January 24, 2015 @Maris, garbage collector as in removing unwanted prefabs/variables? Link to comment Share on other sites More sharing options...
Maris Posted January 24, 2015 Author Share Posted January 24, 2015 Yes. Link to comment Share on other sites More sharing options...
Kzisor Posted January 24, 2015 Share Posted January 24, 2015 @Maris, how do you determine if an item is unwanted? What is the criteria for the unwantedness? I'm trying to determine exactly which events you should listen for in order to make it most efficient. Link to comment Share on other sites More sharing options...
Maris Posted January 25, 2015 Author Share Posted January 25, 2015 @Maris, how do you determine if an item is unwanted? What is the criteria for the unwantedness? I'm trying to determine exactly which events you should listen for in order to make it most efficient.Item is unwanted if:1) There are too many items of the same type in the world2) Item was unused for long time.3) Nobody even see this item for long time.4) There is no players near item. Link to comment Share on other sites More sharing options...
simplex Posted January 25, 2015 Share Posted January 25, 2015 (edited) Item is unwanted if: 1) There are too many items of the same type in the world 2) Item was unused for long time. 3) Nobody even see this item for long time. 4) There is no players near item. ListenFor "entitysleep" -> DoTaskInTime (long time) -> inst:Remove() ListenFor "entitywake" -> cancel above timed callback You may also want to store how much time is left in "long time" as savedata, to keep the garbage collection timer across save reloading. Though, since all entities are awaken at the first game tick, you'd have to be careful in only resuming the "entitywake" event listening until later into the game. But I can't see how achieving what you're trying to achieve would be beneficial, since it's essentially intentional savedata corruption. Edited January 25, 2015 by simplex Link to comment Share on other sites More sharing options...
rezecib Posted January 25, 2015 Share Posted January 25, 2015 (edited) @simplex, I think he's trying to achieve clutter reduction. I think that's the term? In a game where items disappear after a while so the world doesn't get clogged up with tons and tons of items. It's a pretty common mechanic. I'm not sure that modding it in will be very effective, though. Edit: Ah, understood. I mostly agree with you-- I hate having to worry about things timing out on me. I was just thrown off by you calling it "savedata corruption". Edited January 25, 2015 by rezecib Link to comment Share on other sites More sharing options...
simplex Posted January 25, 2015 Share Posted January 25, 2015 @simplex, I think he's trying to achieve clutter reduction. I think that's the term? In a game where items disappear after a while so the world doesn't get clogged up with tons and tons of items. It's a pretty common mechanic. I'm not sure that modding it in will be very effective, though. I get the idea, and I get the performance motivation in reducing entity count (Klei's SelfStacker component was written precisely for that). I just have a strong objection to despawnable entities in a persistent sandbox world. But either way, the good thing about mods is that you'll only use them if you want to, so my subjective view on what's proposed here isn't really significant. Link to comment Share on other sites More sharing options...
outseeker Posted January 25, 2015 Share Posted January 25, 2015 It feels to me that any such thing via mod would consume more CPU than you would gain from losing "clutter", unless the wanted effect was purely visual? Link to comment Share on other sites More sharing options...
rezecib Posted January 25, 2015 Share Posted January 25, 2015 @outseeker, With the right implementation, it may save you some. But it's all up to implementation-- Simplex's suggestion might work, but the first two suggestions would definitely have a bigger overhead than they would be saving. Link to comment Share on other sites More sharing options...
outseeker Posted January 25, 2015 Share Posted January 25, 2015 I thought the way the game worked, clutter would only be relevant to performance if it were within the update radius of the player(s)? I'm probably wrong but I have no problem with being corrected Link to comment Share on other sites More sharing options...
rezecib Posted January 25, 2015 Share Posted January 25, 2015 @outseeker, Well, sleeping entities don't really do anything while asleep, but it does create more things to wake/sleep as players move around. Link to comment Share on other sites More sharing options...
simplex Posted January 26, 2015 Share Posted January 26, 2015 (edited) @outseeker, Well, sleeping entities don't really do anything while asleep, but it does create more things to wake/sleep as players move around.The overhead comes from Lua's garbage collector. Entities (as in "inst.entity") are userdata, which means they are objects exported from the engine, but with their memory allocation handled by Lua's garbage collector. Lua collects garbage incrementally, i.e. in piecewise chunks, but there are some exceptions. Userdata form one of these exceptions: all userdata are checked for deallocation in one pass, which means a larger userdata count causes some periodic stutter. So even asleep entities have a significant impact on performance.Anyway, this is how I'd implement this:local assert = GLOBAL.assertlocal ipairs = GLOBAL.ipairslocal math = GLOBAL.math---local DESPAWN_DELAY = 4*TUNING.TOTAL_DAY_TIME-- Tags which, if present, should make an entity non-despawnable.local FORBIDDEN_TAGS = {"irreplaceable", "nonpotatable"}-- Condition for an entity to be despawnablelocal function is_despawnable(inst) if not inst.components.inventoryitem then return false end for _, tag in ipairs(FORBIDDEN_TAGS) do if inst:HasTag(tag) then return false end end return trueend-----[[-- The callbacks are defined inside, on a per entity basis, in order to-- have access to metadata (such as a variable holding the callback)-- without storing info in the entity itself, which prevents naming-- clashes.--]]local function MakeDespawnable(inst, delay) assert( delay > 0 ) -- Holds the callback. local cb = nil -- Holds the despawn time. local despawn_time = nil --[[ -- This extra layer of indirection is just to play nicely with a -- hypothetical mod overriding inst.Remove dynamically. --]] local function doremove(inst) inst:Remove() end local function onwake(inst) if cb then cb:Cancel() cb = nil end despawn_time = nil end local function onsleep(inst) if cb then assert( despawn_time ) return end local actual_delay if despawn_time then actual_delay = math.max(0, despawn_time - GLOBAL.GetTime()) else actual_delay = delay end cb = inst:DoTaskInTime(actual_delay, doremove) despawn_time = GLOBAL.GetTime() + actual_delay end local function setupevents(inst) inst:ListenForEvent("entitysleep", onsleep) inst:ListenForEvent("entitywake", onwake) if inst:IsAsleep() then onsleep(inst) else onwake(inst) end end local old_onsave, old_onload = inst.OnSave, inst.OnLoad inst.OnSave = function(inst, data) if old_onsave then old_onsave(inst, data) end if despawn_time then data.despawn_delay_left = math.max(0, despawn_time - GLOBAL.GetTime()) end end inst.OnLoad = function(inst, data) if old_onload then old_onload(inst, data) end if data.despawn_delay_left then despawn_time = GLOBAL.GetTime() + data.despawn_delay_left end end inst:DoTaskInTime(0.1 + 0.4*math.random(), setupevents)end---if GLOBAL.TheNet:GetIsMasterSimulation() then AddPrefabPostInitAny(function(inst) if is_despawnable(inst) then MakeDespawnable(inst, DESPAWN_DELAY) end end)endThe above is only applied over items with the inventoryitem component, but this can be easily tweakable by changing the is_despawnable function. Edited January 26, 2015 by simplex Link to comment Share on other sites More sharing options...
Developer bizziboi Posted January 26, 2015 Developer Share Posted January 26, 2015 Lua collects garbage incrementally, i.e. in piecewise chunks, but there are some exceptions. Userdata form one of these exceptions: all userdata are checked for deallocation in one pass Just for your knowledge, DS runs with a modified garbage collector where this is not the case, in order to reduce spikes in the atomic phase. Link to comment Share on other sites More sharing options...
simplex Posted January 27, 2015 Share Posted January 27, 2015 (edited) Just for your knowledge, DS runs with a modified garbage collector where this is not the case, in order to reduce spikes in the atomic phase. Could you clarify? I understand I might be basing myself on outdated info, but my confusion stems from Lua's C API not allowing customisation of the garbage collector, only of the allocator function, so your statement would rely on changing the source code for the Lua VM itself. That is, unless what you're referring to is simply keeping the garbage collector paused and then doing a full collection cycle every N frames (which is a common strategy for games using Lua, so I hear). EDIT: By the way, is there any reason why entities are not light userdata, instead of full userdata? Since entities are explicitly removed ("inst.entity:Retire()"), they need not be garbage collected anyway, so they could be represented via light userdata for performance gains due to removing the garbage collection overhead. To keep the current interface, inst.entity could be a table with a metatable replicating the current engine level entity API, storing the actual entity as a light userdata in, say, "inst.entity[1]", and passing that to the underlying C++ functions. Edited January 27, 2015 by simplex Link to comment Share on other sites More sharing options...
Developer bizziboi Posted January 27, 2015 Developer Share Posted January 27, 2015 @simplex, Yes, DS runs with a modified garbage collector at the C level to reduce the impact of the atomic phase and to allow true timeslicing. The common practice is what DS used to do, but it won`t prevent gc spikes which is why I had to dive in. The garbage collector overhead itself is minimal and fairly controllable now so the gains from using light userdata wrt gc wouldn`t be what I was after (the other overhead related to it might make it worthwhile to explore light userdata but sadly (or rather, fortunately!) I am not scrambling to find tasks to fill my day Link to comment Share on other sites More sharing options...
simplex Posted January 28, 2015 Share Posted January 28, 2015 @simplex, Yes, DS runs with a modified garbage collector at the C level to reduce the impact of the atomic phase and to allow true timeslicing. The common practice is what DS used to do, but it won`t prevent gc spikes which is why I had to dive in. The garbage collector overhead itself is minimal and fairly controllable now so the gains from using light userdata wrt gc wouldn`t be what I was after (the other overhead related to it might make it worthwhile to explore light userdata but sadly (or rather, fortunately!) I am not scrambling to find tasks to fill my day Oh, certainly if the gc overhead wrt entity count was otherwise controlled the performance gains from representing entities via light userdata wouldn't be that large. Some performance, in the form of minimised memory fragmentation and increased locality of reference, could be gained by using light userdata together with a special purpose pool allocator (or by simply storing entities in a deque), but those gains would hardly be significant enough to justify the effort. But was true timeslicing throughout the atomic gc phase achieved by actually editing lua-5.1.5/src/lgc.c, or did you manage to somehow do it though Lua's C API alone? I understand you may be unwilling or unable to discuss implementation details, what I'm mostly interested in is whether DST runs over a modified Lua VM or not. Link to comment Share on other sites More sharing options...
Developer bizziboi Posted January 28, 2015 Developer Share Posted January 28, 2015 But was true timeslicing throughout the atomic gc phase achieved by actually editing lua-5.1.5/src/lgc.c DST definitely uses a modified VM, including lgc.c. Don't get me wrong, the atomic phase itself is not timesliced (trust me I tried) but regular sweep has been timesliced to prevent spikiness when entering single gc steps, and the atomic phase has been shortened by pulling out userdata finalization. I considered publishing the changes as they are different from anything out there (and I am aware of the bitsquid article), but they may only be appliclable to our specific use of udata (and I lack the time to investigate) which is why I didn't. The gains from using a pool allocator for the entities would be minimal (outside of fragmentation), given that most time is spent in the lua code itself and adding enough control to lua to be able to make it so that class members or data access in general would be cache friendly (without changing the lua codebase in ways that would make it very unfun to mod or develop) would seem like a very ambitious stretch. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now