Skip to content

Hides irrelevant parts of maps to drastically improve performance.

License

Notifications You must be signed in to change notification settings

coasterteam/Region-Culling

 
 

Repository files navigation

Region-Culling

Region culling is a system to gradually load and unload large sections of maps. Unloading parts of the map can lead to very high performance improvements since Roblox does not handle this automatically.

Setup

RegionState

RegionState controls what regions are visible. Regions are made of 1 or multiple bounding boxes (CFrame and size) with connections to other regions for visibility. Optionally, they can also be made visible when no regions are visible.

BufferedRegionState is included and used by default. When RegionState makes a region no longer visible, BufferedRegionState will wait a couple of seconds (default is 5 seconds) to reduce regions being unloaded and quickly reloaded. 0 disables buffering entirely, which is useful for testing to ensure region connections aren't missing.

--Location of RegionCulling can be anywhere the client can see.
--Using the main module is not required, but it does reduce setup a bit for most cases.
local RegionCulling = require(game:GetService("ReplicatedStorage"):WaitForChild("RegionCulling"))
local RegionState = RegionCulling.RegionState
local ModelCulling = RegionCulling.ModelCulling

--Optional - Change the time hidden region events are delayed.
RegionState.HiddenRegionTimeout = 2

--Add 3 regions.
--Region 1 is made of 2 zones.
--Region 3 can be seen when the camera is outside the regions.
RegionState:AddRegion("Region1", CFrame.new(0, 0, 0), Vector3.new(2, 2, 2))
RegionState:AddRegion("Region1", CFrame.new(0, 4, 0), Vector3.new(2, 2, 2))
RegionState:AddRegion("Region2", CFrame.new(0, 0, 4), Vector3.new(2, 2, 2))
RegionState:AddRegion("Region3", CFrame.new(0, 0, 6), Vector3.new(2, 2, 2))
RegionState:SetVisibleWhenOutsideRegions("Region3")

--Make it so region 1 can see 2 and 3.
--ConnectRegions makes it so both regions can see each other.
RegionState:ConnectRegions("Region1", "Region2")
RegionState:ConnectRegions("Region1", "Region3")

--Set up the models (next section)

--Start the update loops.
--Camera:GetRenderCFrame() is used for checking where the player is.
RegionCulling:Start()

RegionState can also be read by other scripts.

--Location of RegionCulling can be anywhere the client can see.
--Using the main module is not required, but it does reduce setup a bit for most cases.
local RegionCulling = require(game:GetService("ReplicatedStorage"):WaitForChild("RegionCulling"))
local RegionState = RegionCulling.RegionState

--Listen to events being shown or hidden.
RegionState.RegionVisible:Connect(function(RegionName: string) ... end)
RegionState.RegionHidden:Connect(function(RegionName: string) ... end)

--Check if a region is visible.
local VisibleRegions = RegionState:GetCurrentVisibleRegions() --List of the region names that are visible
local IsVisible = RegionState:IsRegionVisible("Region1") --Bool for if a region is visible
local InRegion = RegionState:IsInRegion("Region1", Vector3.new()) --Bool for if a point is in a region

Secondary Region Setup

RegionState offers an additional method that makes it easier to create a centralized folder of "Regions" that you can easily customize, expand, rotate, resize, and re-position. Depending on use cases, a variety of ways to setup regions is possible. One way is by storing additional regions that can allow you to expand a previous region to an easily customizable size. When rooms are not entire models this may be of use as it allows such behavior.

Regions : Instance
|- Region1 : Instance
|  |-- ZonePart : BasePart
|  |-- ZonePart : BasePart
|- Region2 : Instance
|  |-- ZonePart : BasePart

Using the instance above as an example,

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RegionCulling = require(ReplicatedStorage:WaitForChild("RegionCulling"))
local RegionState = RegionCulling.RegionState

RegionState:InsertRegionsFromInstance(ReplicatedStorage:WaitForChild("Regions"))
--// RegionCulling will generate all regions based on the provided instances.

ModelCulling

ModelCulling controls the hiding of models based on RegionState. Almost all API calls will be to BindModelToRegion, which returns a context for managing additional configuration.

To enable gradual loading/unloading for a model, EnableFlattening in the context must be called. ModelCulling has the number value ReparentOperationsPerStep for how many reparent operations are attempted per update step. A number too high will harm performance when loading/unloading, while a number too low will have visible loading to players. 250 is default, but it may need to be tuned for the specific game. Numbers specific to models can be configured in EnableFlattening.

With model flattening, reparenting operations can be done by calling EnableClustering. It will group parts together into folders of the given size or PartClusterSize in ModelCulling, with 50 as default.

--Location of RegionCulling can be anywhere the client can see.
--Using the main module is not required, but it does reduce setup a bit for most cases.
local RegionCulling = require(game:GetService("ReplicatedStorage"):WaitForChild("RegionCulling"))
local RegionState = RegionCulling.RegionState
local ModelCulling = RegionCulling.ModelCulling

--Optional - change the reparent operations per step and cluster size.
ModelCulling.ReparentOperationsPerStep = 300
ModelCulling.PartClusterSize = 75

--Set up the regions (previous section)

--Bind models.
--Multiple models can be found to a region.
--Can be safely done before and after starting the update loops.
local Context1 = ModelCulling:BindModelToRegion("Region1", game:GetService("Workspace"):WaitForChil("Model1"))
Context1:EnableFlattening() --Enables model flattening with the default reparent operations per step.
Context1:EnableClustering() --Enables clustering while flattening with the default cluster size.

local Context2 = ModelCulling:BindModelToRegion("Region2", game:GetService("Workspace"):WaitForChil("Model2"))
Context2:EnableFlattening(500) --Enables model flattening with a custom reparent operations per step.
Context2:EnableClustering(100) --Enables clustering while flattening a custom default cluster size.

local Context3 = ModelCulling:BindModelToRegion("Region2", game:GetService("Workspace"):WaitForChil("Model2A"))
--Context2:EnableFlattening() --Flattening is NOTT enabled. EnableClustering() has no effect without EnableFlattening().

--Add filters for what parts to flatten.
--Filters return true if an instance can be flattened, with an optioanl string saying why they can't.
--Be aware a single instance not being flattened will result in the entire instance tree under the model being unflattened (see next section).
--Context3 is not passed in because EnableFlattening() was not called.
for _, Context in {Context1, Context2} do
    --A filter for Humanoids is not added because Humanoids are special-cased to move together.
    Context:AddFlatteningFilter(function(Child: Instance): (boolean, string?)
        return not Child:IsA("BasePart") or Child.Anchored, "Part is not anchored."
    end)
    Context:AddFlatteningFilter(function(Child: Instance): (boolean, string?)
        return not Child:IsA("BaseScript"), "Scripted instances are not safe to be flattened."
    end)
end

--Start the update loops.
--Camera:GetRenderCFrame() is used for checking where the player is.
RegionCulling:Start()

Diagnosing Performance With AddFlatteningFilter

When AddFlatteningFilter is used, it can cause large amounts of instances to not be gradually loaded and unloaded. As an example, if Workspace.Model is bound to a region and Workspace.Model.SubModel1.SubModel2.SubModel3.Part fails the filter, the entire model under Workspace.Model.SubModel1 will be stuck loading and unloading together, even if only a single instance is a problem.

To diagnose problems, use ModelCulling:GetSummary(). It returns a dictionary of region names to a list of ModelCullingContextSummary. ModelCullingContextSummary contains the instance (Model), number of flattened parts (FlattenedParts), number of unflattened parts (UnflattenedParts), and a list of ModelCullingIssue (Issues). Each ModelCullingIssue will include the problematic instance (Instance) and a list of all the filter reasons (Issues). Sorting by the most UnflattenedParts by model (NOT by region) will show the most problems. With the issues, either move the problematic models to separate models or change the filter to allow them through.

The results of ModelCulling:GetSummary() may change over time, either because of scripts, unanchored parts being removed, or instance streaming. Clustering is done 1 model every 1/30th of a second by default with a delay of 5 seconds (can be changed with ModelCulling.PassiveModelFlattenDelay) before retrying. Waiting may be required before getting a proper reading.

License

This project is available under the terms of the MIT License. See LICENSE for details.

About

Hides irrelevant parts of maps to drastically improve performance.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Lua 100.0%