How could I squeeze as much optimization out as possible in this module? It is a plot generation system that generates some plots and streams them out/in based on their position.
Module:
-- Services --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Modules --
local Serializer = require(ReplicatedStorage.Assets.Modules.RBLXSerialize)
local MapInfo = require(ReplicatedStorage.Assets.PlotMap)
local LoaderPool = require(ReplicatedStorage.Assets.Modules.LoaderPool)
local Pool = ReplicatedStorage
-- Types --
export type PlotInfo = {
Model : Model,
Chance : number,
Size : Vector2
}
export type Entry = {
x : number,
y : number,
sizex : number,
sixey : number,
rot : number,
id : string,
intercept : number,
cf : CFrame,
_instance : Model?
}
export type GenerateWorld = {
Seed : Random,
GridSizeX : number,
GridSizeY : number,
BaseSize : number,
Plots : {[any] : PlotInfo},
PlacedData : {Entry},
SpawnPillars : boolean,
CeilingHeight : number,
new : (Seed : number?) -> GenerateWorld,
GenerateAsync : (self : GenerateWorld) -> (),
StreamAround : (self : GenerateWorld, Position : Vector3, radius : number) -> (),
Serialize : (self : GenerateWorld, Model : Model) -> string,
UnSerialize : (self : GenerateWorld, Id : string) -> Model
}
local GenerateWorld = {}
GenerateWorld.__index = GenerateWorld
local ROTS = {
0,
90,
180,
270
}
local function ChooseRandom(rng : Random, info : {PlotInfo}, size : Vector2?)
local ndef = {}
if size then
for i, v in info do
if v.Size == size then
table.insert(ndef, v)
end
end
else
ndef = info
end
if #ndef == 0 then
return MapInfo.EmptyPlot
end
local t = 0
for _, e in pairs(ndef) do
if type(e) == "number" then continue end
t += e.Chance
end
local random = rng:NextNumber() * t
for _, f in pairs(ndef) do
if type(f) == "number" then continue end
random -= f.Chance
if random <= 0 then return f end
end
return MapInfo.EmptyPlot
end
local function Rotate(rng : Random)
return ROTS[rng:NextInteger(1, #ROTS)]
end
local function IsPillarCell(self, x, y)
if not self.SpawnPillars then return false end
local spacing = MapInfo.Pillar.Spacing + 1
local dx = (x - self._pillarOffsetX) % spacing
local dy = (y - self._pillarOffsetY) % spacing
return (dx == 0) and (dy == 0)
end
local function Check(self : GenerateWorld, x : number, y : number, sizex : number, sizey : number) : boolean
if x < 1 or y < 1 or sizex < 1 or sizey < 1 then return false end
if x + sizex - 1 > self.GridSizeX or y + sizey - 1 > self.GridSizeY then return false end
for i = x, x + sizex - 1 do
for j = y, y + sizey - 1 do
if self.Occupied[i][j] then return false end
if IsPillarCell(self, i, j) then return false end
end
end
return true
end
local function Mark(self : GenerateWorld, x : number, y : number, sizex : number, sizey : number) : boolean
for i= x, x + sizex - 1 do
for j = y, y + sizey - 1 do
self.Occupied[i][j] = true
end
end
end
local function Init(Folder : {Model} | Folder) : {PlotInfo}
assert(Folder, "no folder!!!")
local tbl = {}
if type(Folder) == "table" then
tbl = Folder
else
tbl = Folder:GetChildren()
end
if not tbl then return end
local data = {}
for i, v : PlotInfo in MapInfo do
local check = v.Model
for _, b in tbl do
if check == b then
table.insert(data, v)
end
end
end
return data
end
local function SetSpawns(rng : Random, Model : Model)
for i, v in Model:GetDescendants() do
if v:IsA("SpawnLocation") then
if rng:NextNumber() > 0.2 then v:Destroy() continue end
v.CanCollide = false
v.CanQuery = false
v.Parent = workspace.Map.Map.Spawns
end
end
end
local function CloneTable(tbl : {any}) : {any}
local new = {}
for i, v in tbl do
table.insert(new, v)
end
return new
end
function GenerateWorld.new(seed, gridSize, plotlist, ceilingheight, spawnpillar)
local self = setmetatable({}, GenerateWorld)
self.Seed = seed and Random.new(seed) or Random.new()
local grid = gridSize or Vector2.new(52,52)
self.GridSizeX = math.clamp(grid.X, 1, 52)
self.GridSizeY = math.clamp(grid.Y, 1, 52)
self.BaseSize = 27.6
self.CeilingHeight = ceilingheight or 2020
self.SpawnPillars = spawnpillar or true
local spacing = MapInfo.Pillar.Spacing
self._pillarOffsetX = self.Seed:NextInteger(1, spacing)
self._pillarOffsetY = self.Seed:NextInteger(1, spacing)
self.Occupied = {}
for i=1,self.GridSizeX do
self.Occupied[i] = {}
for j=1,self.GridSizeY do
self.Occupied[i][j] = false
end
end
local size = 4
local cols = math.ceil(self.GridSizeX / size)
local rows = math.ceil(self.GridSizeY / size)
local actor = require(script.ChunkActor)
self.ChunkSize = size
self.Chunks = {}
for i = 1, cols do
self.Chunks[i] = {}
for j = 1, rows do
local originX = (i-1)*size + 1
local originY = (j-1)*size + 1
self.Chunks[i][j] = actor.new(self, originX, originY, size)
end
end
self.Plots = Init(plotlist) or { MapInfo.EmptyPlot }
self.PlacedData = {}
return self
end
function GenerateWorld:Finalize()
for _, entry in ipairs(self.PlacedData) do
local ci = math.floor((entry.x - 1) / self.ChunkSize) + 1
local cj = math.floor((entry.y - 1) / self.ChunkSize) + 1
local chunk = self.Chunks[ci] and self.Chunks[ci][cj]
if chunk then
chunk:Add(entry)
end
end
end
function GenerateWorld.ScalePillar(model : Model, height : number)
local main = model:FindFirstChild("Pillar_Main", true)
if not main or not main:IsA("BasePart") then return end
local old = main.Size
local new = Vector3.new(old.X, height, old.Z)
local delta = (new.Y - old.Y)/2
main.Size = new
main.CFrame = main.CFrame * CFrame.new(0, delta, 0)
end
function GenerateWorld:Serialize(model)
local id = Serializer.Encode(model, true)
model:Destroy()
return id
end
function GenerateWorld:UnSerialize(id)
local model = Serializer.Decode(id, true)
model.Parent = workspace.Map.Map
return model
end
local function _GEN(self, x)
local y = 1
local spacing = MapInfo.Pillar.Spacing
local rng = self.Seed
while y <= self.GridSizeY do
local info, rotation, sizex, sizey
if IsPillarCell(self, x, y) then
local new = CloneTable(MapInfo.Pillar)
new.Spacing = nil
info = ChooseRandom(rng, new)
rotation, sizex, sizey = 0, 1, 1
Mark(self, x, y, 1, 1)
else
info = ChooseRandom(rng, self.Plots)
rotation = Rotate(rng)
local swapped = (rotation == 90 or rotation == 270)
sizex = swapped and info.Size.Y or info.Size.X
sizey = swapped and info.Size.X or info.Size.Y
if not Check(self, x, y, sizex, sizey) then
y += 1
continue
end
Mark(self, x, y, sizex, sizey)
end
local clone = info.Model:Clone()
GenerateWorld.ScalePillar(clone, self.CeilingHeight)
local worldx = ((x-1) + sizex/2) * self.BaseSize
local worldy = ((y-1) + sizey/2) * self.BaseSize
local bottom = clone:GetExtentsSize().Y/2
local cf = CFrame.new(worldx, bottom, worldy) * CFrame.Angles(0, math.rad(rotation), 0)
clone:PivotTo(cf)
SetSpawns(rng, clone)
local id = self:Serialize(clone)
local intercept = math.sqrt((sizex*self.BaseSize/2)^2 + (sizey*self.BaseSize/2)^2)
table.insert(self.PlacedData, {
x = x,
y = y,
rot = rotation,
id = id,
cf = cf,
intercept = intercept,
_instance = nil,
})
y += sizey
end
end
function GenerateWorld:GenerateAsync()
for x=1,self.GridSizeX do
task.wait(0.1)
task.spawn(_GEN, self, x)
end
for x=1,self.GridSizeX do
for y=1,self.GridSizeY do
if not self.Occupied[x][y] then
self.Occupied[x][y] = true
local info = ChooseRandom(self.Seed, self.Plots, Vector2.new(1,1))
local clone = info.Model:Clone()
local rot = Rotate(self.Seed)
local worldx = ((x-1)+.5)*self.BaseSize
local worldy = ((y-1)+.5)*self.BaseSize
local bottom = clone:GetExtentsSize().Y/2
local cf = CFrame.new(worldx,bottom,worldy)*CFrame.Angles(0,math.rad(rot),0)
clone:PivotTo(cf)
local id = self:Serialize(clone)
local intercept = math.sqrt((1*self.BaseSize/2)^2 + (1*self.BaseSize/2)^2)
table.insert(self.PlacedData, {
x = x,
y = y,
rot = rot,
id = id,
cf = cf,
intercept = intercept,
_instance = nil
})
end
end
end
end
function GenerateWorld:StreamAround(position, radius)
local gridx = math.clamp(math.floor(position.X / self.BaseSize) + 1, 1, self.GridSizeX)
local gridy = math.clamp(math.floor(position.Z / self.BaseSize) + 1, 1, self.GridSizeY)
local ci = math.floor((gridx - 1) / self.ChunkSize) + 1
local cj = math.floor((gridy - 1) / self.ChunkSize) + 1
local r = math.ceil(radius / (self.BaseSize * self.ChunkSize))
for i = ci - r, ci + r do
for j = cj - r, cj + r do
local chunk = self.Chunks[i] and self.Chunks[i][j]
if chunk then
chunk:Update(position, radius)
end
end
end
end
return GenerateWorld
Also im having problems with the actor, as sometimes it leaves plots behind for seemingly no reason? (basically they dont deload), and sometimes plots that should load dont?
actor:
-- Services --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Modules --
local LoaderPool = require(ReplicatedStorage.Assets.Modules.LoaderPool)
local GenerateWorld = require(ReplicatedStorage.Assets.Modules.GenerateWorld)
-- Main --
local ChunkActor = {}
ChunkActor.__index = ChunkActor
function ChunkActor.new(world : GenerateWorld, originx : number, originy : number, size)
local self = setmetatable({}, ChunkActor)
self._world = world
self.originx = originx
self.originy = originy
self.size = size
self.entries = {}
return self
end
function ChunkActor:Add(entry)
entry._world = self._world
table.insert(self.entries, entry)
end
function ChunkActor:InRange(wpos, radius, entry)
local pos = entry.cf.Position
local Distance = (Vector3.new(pos.X, wpos.Y, pos.Z) - wpos).Magnitude
return Distance <= (radius + entry.intercept)
end
function ChunkActor:LoadEntry(e)
LoaderPool.Enqueue(function()
local inst = e._world:UnSerialize(e.id)
if e._world.SpawnPillars and e.sizex == 1 and e.sizey == 1 then
e._world.ScalePillar(inst, e._world.CeilingHeight)
end
inst:PivotTo(e.cf)
inst.Parent = workspace.Map.Map
e._instance = inst
end)
end
function ChunkActor:UnloadEntry(e)
LoaderPool.Enqueue(function()
e.id = e._world:Serialize(e._instance)
e._instance:Destroy()
e._instance = nil
end)
end
function ChunkActor:Update(pos, radius)
for _, e in ipairs(self.entries) do
local range = self:InRange(pos, radius, e)
if range and not e._instance then
self:LoadEntry(e)
elseif not range and e._instance then
self:UnloadEntry(e)
end
end
end
return ChunkActor
thank you for the help