How to remedy attempt to yield across metamethod/C-call boundary when trying to Pathfind

I’m trying to make a system for which SCPs (Humanoids) can pathfind to valid targets based on modes of operation.

I have a table called validTargets, whenever a new target is added to that table; I want to check if it is the closest target or not to the SCP (aka Humanoid I’m trying to pathfind from).

What I’m doing in the script below is, I have a __newindex metamethod that calls a function that generates a new target based on the mode of operation. Then that function calls another function to generate a path to that new target, which then calls another function to start the moving sequence.

I’m getting the following error: attempt to yield across metamethod/C-call boundary

local services = {
	pathfind = game:GetService("PathfindingService"),
	run = game:GetService("RunService")
}

local SCP = script.Parent
local SCPHumanoid = script.Parent:FindFirstChildWhichIsA("Humanoid")
local SCPHumanoidRootPart = script.Parent:FindFirstChild("HumanoidRootPart")

local status = {
	walking = false,
	attacking = false,
	chasing = false
}

local config = {
	canAttack = true,
	canAnimate = true,
	canChase = true,
	mode = 1
}

--[[
1: Target closest Humanoid
2: Target Humanoid with least health
3: Target Humanoid with most health
4: Target specific Humanoid
--]]

local targetsFolder : Folder = workspace.TargetsV2

local agentParam = {
	AgentRadius = 2,
	AgentHeiht = 5,
	AgentCanJump = true,
	AgentCanClimb = true,
	WaypointSpacing = 3,
	Costs = {
		Water = 100,
		Air = 5,
		DangerZone = math.huge
	}
}

local mt = {
	__newindex = function(self, index, value)
		print(value)
		rawset(self, index, value)
		generateTrueTarget(self)
	end
}
local validTargets = setmetatable({}, mt)

local validTargetCheckThread = task.spawn(function()
	local pathObj : Path = services.pathfind:CreatePath(agentParam)

	while task.wait() do
		for i, v : Part in ipairs(targetsFolder:GetChildren()) do
			pathObj:ComputeAsync(SCPHumanoidRootPart.Position, v.Position)
			
			if table.find(validTargets, v) then
				continue
			end
			
			if pathObj.Status ~= Enum.PathStatus.NoPath then
				validTargets[#validTargets + 1] = v
			end
		end
		
		print(validTargets)
	end
end)

function generateTrueTarget()
	print("Generating true target")
	local foundClosest = -1

	if config.mode == 1 then
		for i, v in validTargets do
			if foundClosest == -1 then
				foundClosest = v
				continue
			end
			
			if (SCPHumanoidRootPart.Position - v.Position).Magnitude < (SCPHumanoidRootPart.Position - ((foundClosest.Position) or math.huge)).Magnitude then
				foundClosest = v
			end
		end
	end
	
	generatePath(foundClosest)
end

function generatePath(target : Part)
	local mainPathObj : Path = services.pathfind:CreatePath(agentParam)
	moveTo(mainPathObj:ComputeAsync(SCPHumanoidRootPart.Position, target.Position))
end

function moveTo(generatedPath : Path)
	
	local generatedPathWaypoints = generatedPath:GetWaypoints()
	
	for i, point : PathWaypoint in ipairs(generatedPathWaypoints) do
		SCPHumanoid:MoveTo(point.Position)
		print("Moving to point " .. i)
		
		if point.Action == Enum.PathWaypointAction.Jump then
			SCPHumanoid.Jump = true
		end
	end
end

Any ideas on how I can remedy this? I know that error is for when you try to call a non-yielding function inside a metamethod.

1 Like

metamethods doesn’t support yielding unfortunately (the __newindex) so you will need to think of an alternative way of calling generateTrueTarget when setting something on validTargets or just wrap the generateTrueTarget on a new thread (task.spawn)

1 Like

I did think of that, but I’m worried that it’ll generate too much lag; would it generate big performance hit if I just wrapped generateTrueTarget like this:

local generateTrueTargetThread = task.spawn(function()
	while task.wait() do
		generateTrueTarget(validTargets)
	end
end)

bruh

generateTrueTarget(self)task.spawn(generateTrueTarget, self)

Thing is, won’t I need to wrap it in a while task.wait() do aka a while loop? Since, if I only call it once; then how would it be ran once validTargets has changed? For example:

  • generateTrueTarget() runs
  • a new target is added to validTargets
  • since it has already run, it doesnt run again

ok so this

local mt = {
	__newindex = function(self, index, value)
		print(value)
		rawset(self, index, value)
		generateTrueTarget(self)
	end
}

to

local mt = {
	__newindex = function(self, index, value)
		print(value)
		rawset(self, index, value)
		task.spawn(generateTrueTarget, self)
	end
}
1 Like

Ah, now I get it. Thank you.

ineedthirtycharacters

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.