Hey,

I have this Dijkstra pathfinding algorithm and it works fine and all no lag or whatsoever when I play in studio but online it's acting really buggy.

The NPC doesn't walk like a human it sort of lags forwards. Here, if you watch this gif you'll probably see what I mean.

This is all on the server side so other players can see the same NPC. The question being how would I reduce this amount of lag/bugginess?

Gif: https://gyazo.com/886652cda6b734a19e9ad806ba7ce3f6

Here's my module script:

local module = {}
module.__index = module

local Nodes = workspace.Tiles
local Spawn

local PhysicsService = game:GetService("PhysicsService")
local playerCollisionGroupName = "NPCs"
PhysicsService:CreateCollisionGroup(playerCollisionGroupName)
PhysicsService:CollisionGroupSetCollidable(playerCollisionGroupName, playerCollisionGroupName, false)

local function Tablecontains(table, element)
    for i,v in pairs(table) do
        if table == element then
            return true
        end
    end
    return false
end

local function setCollisionGroupRecursive(object)
	if object:IsA("BasePart") then
		PhysicsService:SetPartCollisionGroup(object, playerCollisionGroupName)
	end
	for _, child in ipairs(object:GetChildren()) do
		setCollisionGroupRecursive(child)
	end
end


local function CreatePool(Type)
    local AssetsGuest = game.ReplicatedStorage.AssetsGuest
    local IslandGuest = AssetsGuest[Type]:Clone()
    IslandGuest.Parent = workspace.LocalDump

	IslandGuest.Head.Ui.Txt.Text = IslandGuest.Name

    local SpawnPositions = workspace.Tiles
    local Index = math.random(1, #SpawnPositions:GetChildren())
    local ChosenPoint = SpawnPositions:GetChildren()[Index]

	Spawn = ChosenPoint

    IslandGuest:SetPrimaryPartCFrame(CFrame.new(ChosenPoint.Position) + Vector3.new(0, 5, 0))

    return IslandGuest
end

function module:New(Type)
	local self = {
		Model = CreatePool(Type),
		Start = Spawn,
	}
	setmetatable(self, module)
	
	self.RootPart = self.Model:FindFirstChild("HumanoidRootPart")
	self.Humanoid = self.Model:FindFirstChild("Humanoid")
	
	setCollisionGroupRecursive(self.Model)
	
	return self
end

local WaitForMoveToFinished
do
	local RunService = game:GetService("RunService")	
	
	function WaitForMoveToFinished(self, position, timeout)
		local initTime = tick()
		timeout = timeout or 1e3
		
		local position2D = Vector3.new(position.X, self.RootPart.Position.Y, position.Z)
		
		while ((self.RootPart.Position - position2D).magnitude > 2 and tick() - initTime < timeout) do
			RunService.Heartbeat:Wait()
		end
	end
end

local function Dijkstra(nodes, start, target)
    local dist, prev = {},{}
    local Q = {}
    for i,v in ipairs(nodes:GetChildren()) do
        dist[v] = math.huge
        Q[v] = true
    end
    dist[start] = 0
    while next(Q) do
        local u
        local least = math.huge
        for v, i in pairs(Q) do
            if dist[v] < least then
                least = dist[v]
				u = v
            end
        end
        if u == target then
            break
        end
        Q[u] = nil
        for i,v in pairs(u:GetConnectedParts()) do
            local alt = dist[u] + (v.Position-u.Position).Magnitude
            if alt < dist[v] then
                dist[v] = alt
                prev[v] = u
            end
        end
    end
    local S = {}
    local u = target
    while prev[u] do
        table.insert(S, 1, u)
        u = prev[u]
    end
    table.insert(S, 1, u)
    return S
end

function module:Roam(self)
	spawn(function()
		local TargetIndex = math.random(1, #Nodes:GetChildren())
		local Target = Nodes:GetChildren()[TargetIndex]
		
		repeat
			TargetIndex = math.random(1, #Nodes:GetChildren())
		until (Nodes:GetChildren()[TargetIndex].Position - self.RootPart.Position).magnitude > 8
		
		local MoveTo = Dijkstra(Nodes, self.Start, Target)
		
		for i,v in pairs(MoveTo) do
			self.Humanoid:MoveTo(v.Position)
			
			WaitForMoveToFinished(self, v.Position, 2)
			self.Start = v
		end
		
		spawn(function() module:Roam(self) end)
	end)
end

return module

Here's my module script which spawns all the NPCs:

local module = {}

local ModuleScript = require(script.Parent.ModuleScript)
local GhostTypes = require(game.ReplicatedStorage.GhostTypes)

local Types = GhostTypes.Types


local MaxNPCs = 1
local GhostIndex = 0

local NewTypes = {}

function module:InitializeAction()
	for i,v in pairs(Types) do
		GhostIndex = GhostIndex + 1
		table.insert(NewTypes, v)
	end
	
	while (true) do	
		local ActiveNPCs = workspace.LocalDump:GetChildren()
		
		local random = math.random(1, GhostIndex)		
		
		local spawnChance = NewTypes[random].Chance
		local Type = NewTypes[random].Type
		
		if (math.random(0, 100)/100 <= spawnChance) and #ActiveNPCs < MaxNPCs then
			spawn(function()
				local guest = ModuleScript.New(spawnChance, Type)
				
				spawn(function() guest:Roam(guest) end)
			end)
		end
		wait(1.5)
	end
end


return module