Added instantiate scene into Array and when adding child into scene they're empty (Nothing is found in Array)

Hi ! This is Godot v 4.7

EDIT 1: Updated this to what’s already tested to avoid duplicated to someone who recently viewed this and rephrasing my words to make it more clear

I tried to added instantiated scene into array, it’s working well and fine when playing for few seconds-minute in. The bug (the title stated) occured, and it’s only occured on one customer in party (The game generated random amount of customers)

Customer and Individual Customer generation

func _on_customer_spawn_cooldown_timeout() -> void:
	var customer_generated_amount = 0
	var randomize_spawn = rng.randi_range(1,3)
	if Global.current_customer_waitinginline < children_inspawn.size() and  Global.current_customer_inscene < Maxed_customer_inscene:
		match randomize_spawn:
			1:
				if Global.av_big_table > 0:
					customer_generated_amount = rng.randi_range(3,5)
					Global.av_big_table -= 1
			2:
				if Global.av_mid_table > 0:
					customer_generated_amount = 2
					Global.av_mid_table -= 1
			3:
				if Global.av_small_table > 0: 
					customer_generated_amount = 1
					Global.av_small_table -= 1
			_: print_debug("Nothing is spawned")
	
		var CUS = CUSTOMER.instantiate()
		for i in customer_generated_amount:
			var INV_CUS = INVID_CUSTOMER.instantiate()
			CUS.customer_inparty.append(INV_CUS)
			print_debug("Customer in party in spawn:","at",i,"index",CUS.customer_inparty)
			print_debug(INV_CUS.get_instance_id())
			print_debug(is_instance_valid(INV_CUS))
		for i in children_inspawn:
			if  i.get_child_count() == 0:
				i.add_child(CUS)
				print_debug("Customer in party in spawning into marker:",CUS.customer_inparty) 
				print_debug(i.get_instance_id())
				print_debug(is_instance_valid(i))
				Global.current_customer_waitinginline += 1
				Global.current_customer_inscene += customer_generated_amount
				break

This code is working fine when debugged, the customer spawned in correct amount and can be seen. But the problem ONLY when it’s added into table itself and it’s only happen to one customer sometimes and it’s fixed itself some table in the same table (Customer queue freed when everyone in the same table have recieved their orders)

On the second print_debug lines of “Customer in party in spawn” the Individual Customer disappred from Customer Array

extends Control

var table_isavaliable: bool = true
var customer_inchairtaken: Array = [] #Use for clearing customer out
var customers_taken: Dictionary = {}
enum TYPES {Small,Mid,Big}
@export var table_type: TYPES
@onready var chair_location = $Chair_avaliable.get_children()
@onready var number_ofchairs =  len($Chair_avaliable.get_children())
enum STATES {Freed, Taken}
var index:int = 0
var amount_ofcustomer:int = 0
var amount_ofcustomertaken:int = 0

func _ready() -> void:
	checking_orders()

func _process(delta: float) -> void:
	#TODO check if all customer has successully taken their orders
	if not customers_taken.is_empty():
		checking_orders()
	#TODO then queue freed customer / removed taken to empty
			#print_debug(customers_taken)
		if amount_ofcustomertaken == amount_ofcustomer:
			for i in customer_inchairtaken:
				if is_instance_valid(i):
					Global.current_customer_inscene -= 1
					print_debug("Customer in party in table cleared",customers_taken) 
					i.queue_free()
			customers_taken = {}
			table_isavaliable = true
			
			if amount_ofcustomer >= 3 and amount_ofcustomer <= 6:
				Global.av_big_table += 1
			elif amount_ofcustomer == 2:
				Global.av_mid_table += 1
			elif amount_ofcustomer == 1: 
				Global.av_small_table += 1

func take_chair(customer) -> void:
	print_debug("Customer in party in pre-assigned table", customer.customer_inparty)
	print_debug(is_instance_valid(customer.customer_inparty))
	if is_instance_valid(customer):
		if not customer.customer_inparty.is_empty():
			for i in customer.customer_inparty:
				print_debug("Customer in party in assigned table", customer.customer_inparty)
				print_debug(i.get_instance_id())
				print_debug(is_instance_valid(i))
				if i != null:
					var  CUS = i
					chair_location[index].add_child(CUS)
					#print_debug(CUS.global_position)
					customer_inchairtaken.append(CUS)
					CUS.inside_table = true
					customers_taken[CUS] = STATES.Freed
					Global.data_tracking["Customer taken"] += 1 
					index += 1
				else:
					Global.av_small_table += 1
					Global.current_customer_inscene -= 1
			index = 0
			table_isavaliable = false
			amount_ofcustomer = len(customers_taken)

func checking_orders():
	amount_ofcustomertaken = 0
	for keys in customers_taken:
		if is_instance_valid(keys):
			var value = customers_taken[keys]
			if keys.order_recieved:
				customers_taken[keys] = STATES.Taken
			match value:
				STATES.Taken:
					amount_ofcustomertaken += 1
				STATES.Freed:
					amount_ofcustomertaken -= 1

I tried somewhat making placed customer instantiate and add child and it doesn’t help anything (same bug still occured)

Comparsion between Normal and Bugged


The code in the picture is in older version, but everything still working the same as current version

Any help would be appreciate! as i have finally hit rock bottom with this

PS. if anyone struggled reading my english/grammar please also do let me know as i can rephrased the confusion you have

When the bug happens, could you print customer_inparty right at that moment and see what’s actually sitting in the slot? A node that’s been queue_freed stays in the array as <Freed Object> rather than going null or dropping out, so the array can still look the right size while that entry is dead. A plain null check won’t catch it, is_instance_valid(i) is the one I’d use.

From what I can see nothing takes entries back out of customer_inparty, so when a table clears and those customers get freed the array keeps holding them. That could explain why it only lands on one of them.

Sure thing!, I have printed on the first line after func of take chair and this is result

image
the “Customer:<CharacterBody2D#334504134805>” is belonged to add_customer() line

func take_chair(customer) -> void: #top part of take chair from table line
	print_debug(customer,customer.customer_inparty) #line 39
	if is_instance_valid(customer):
		print_debug(customer.customer_inparty) #line 41
		if not customer.customer_inparty.is_empty(): 
			for i in customer.customer_inparty:

And the table do queue freed everything when in its Array when everything cleared (like what you said), not sure what’s holding them if that’s the case

	if not customers_taken.is_empty(): 
		checking_orders()
		if amount_ofcustomertaken == amount_ofcustomer:
			for i in customers_taken:
				#Global.current_customer_inscene -= 1
				i.queue_free()
				customers_taken = {}
			table_isavaliable = true

Not enough context. Post the entire script.

Edit: Accidentally replied to @Baz instead to @WSSA

Can you print the array immediately after append(), immediately before add_child(), immediately after add_child(), and immediately after every queue_free()? Also print get_instance_id() and is_instance_valid() for each entry. That should tell us whether the reference is actually disappearing from the array or whether the Node itself is becoming invalid.

Oops apologized! pretty new to this website let me post entire script

full script of add_customer

var CUS = CUSTOMER.instantiate() #BASELINE
var CUSTO = CUSTOMER.instantiate()

func _on_customer_spawn_cooldown_timeout() -> void:
	var customer_generated_amount = 0
	var randomize_spawn = rng.randi_range(1,3)
	#var randomize_spawn = 3
	if Global.current_customer_waitinginline < children_inspawn.size() and  Global.current_customer_inscene < Maxed_customer_inscene:
		match randomize_spawn:
			1:
				if Global.av_big_table > 0:
					customer_generated_amount = rng.randi_range(3,5)
					Global.av_big_table -= 1
			2:
				if Global.av_mid_table > 0:
					customer_generated_amount = 2
					Global.av_mid_table -= 1
			3:
				if Global.av_small_table > 0: 
					customer_generated_amount = 1
					Global.av_small_table -= 1
		CUS = CUSTOMER.instantiate()
		for i in customer_generated_amount:
			CUSTO = CUSTOMER.instantiate()
			print_debug(CUSTO)
			CUS.customer_inparty.append(CUSTO)
		for i in children_inspawn:
			if  i.get_child_count() == 0:
				i.add_child(CUS)
				Global.current_customer_waitinginline += 1
				Global.current_customer_inscene += customer_generated_amount
				break

entire full script of Table/placed customer (i’m suspected there’s something wrong with this)

extends Control

var table_isavaliable: bool = true
#var chair_location: Array = []
var customers_taken: Dictionary = {}
enum TYPES {Small,Mid,Big}
@export var table_type: TYPES
@onready var chair_location = $Chair_avaliable.get_children()
@onready var number_ofchairs =  len($Chair_avaliable.get_children())
enum STATES {Freed, Taken}
var index:int = 0
var amount_ofcustomer:int = 0
var amount_ofcustomertaken:int = 0

func _ready() -> void:
	checking_orders()

func _process(delta: float) -> void:
	#TODO check if all customer has successully taken their orders
	if not customers_taken.is_empty():
		checking_orders()
	#TODO then queue freed customer / removed taken to empty
			#print_debug(customers_taken)
		if amount_ofcustomertaken == amount_ofcustomer:
			for i in customers_taken:
				Global.current_customer_inscene -= 1
				i.queue_free()
				customers_taken = {}
			table_isavaliable = true
			
			if amount_ofcustomer >= 3 and amount_ofcustomer <= 6:
				Global.av_big_table += 1
			elif amount_ofcustomer == 2:
				Global.av_mid_table += 1
			elif amount_ofcustomer == 1: 
				Global.av_small_table += 1

func take_chair(customer) -> void:
	print_debug(customer,customer.customer_inparty)
	if is_instance_valid(customer):
		print_debug(customer.customer_inparty)
		if not customer.customer_inparty.is_empty():
			for i in customer.customer_inparty:
				if i != null:
		#print_debug(chair_location[index],i)
					var  CUS = i
		#customer.global_position = chair_location[index]
					chair_location[index].add_child(CUS)
					print_debug(CUS.global_position)
					CUS.inside_table = true
					customers_taken[CUS] = STATES.Freed
					Global.data_tracking["Customer taken"] += 1 
					index += 1
				else:
					Global.av_small_table += 1
					Global.current_customer_inscene -= 1
			index = 0
			table_isavaliable = false
			amount_ofcustomer = len(customers_taken)

func checking_orders():
	amount_ofcustomertaken = 0
	for keys in customers_taken:
		if is_instance_valid(keys):
			var value = customers_taken[keys]
			if keys.order_recieved:
				customers_taken[keys] = STATES.Taken
			match value:
				STATES.Taken:
					amount_ofcustomertaken += 1
				STATES.Freed:
					amount_ofcustomertaken -= 1

Can we see the Customer script? My guess is that it resets customer_inparty.

Sure, i’m in the process of doing the print you’re requested too , it might take a while for me to fully format everything but you can take a look to this first :heart_hands:

extends CharacterBody2D

var speed: int = 10


var rng = RandomNumberGenerator.new()
var menu_i_want:Array #TODO generated into array
@onready var limit_i_can: int = 1 #rng.randi_range(1,3)
var has_ordered: bool = false
var inside_table:bool = false
var customer_inparty:Array = []
var result_string: String
var order_recieved: bool = false

const NPC_1 = preload("uid://dvx33pc5j0y18")
const NPC_2 = preload("uid://c1d2vvqey7uai")
const NPC_6 = preload("uid://dlsng01br1f6m")

func _ready() -> void: 
	#self modulate depends on amount of customer in party
	if len(customer_inparty) >= 3 and len(customer_inparty) <= 6:
		$Sprite2D.texture = NPC_6
		print_debug("Big generation")
	elif len(customer_inparty) > 1:
		$Sprite2D.texture = NPC_2
		print_debug("mid generation")
	elif len(customer_inparty) <= 1: 
		$Sprite2D.texture = NPC_1
		print_debug("small generation")
	#TODO the moment customer entered the shop, the timer started
	$ProgressBar.max_value = $Impatient_counter.wait_time
	$Impatient_counter.start()
	print_debug(customer_inparty)
	
func _process(delta: float) -> void:
	$ProgressBar.value = $Impatient_counter.time_left
	if inside_table and not has_ordered and menu_i_want.is_empty():
		$Panel.visible = true
		$"Customer area".monitorable = false
		$"Customer area".monitoring = false
		$"Customer area/CollisionShape2D".disabled = true
		random_gen_menu()
		print_debug("I ordered")
		has_ordered = true
		menu_gen_text()
		$Panel/Label.text = result_string

	if inside_table and has_ordered and menu_i_want.is_empty():
		order_recieved = true
		$Impatient_counter.stop()
		$Panel.visible = false

func menu_gen_text():
	for item in menu_i_want:
			result_string += str(item.name)

func random_gen_menu():
	print_debug(limit_i_can)
	for i in limit_i_can:
		var random_order = Global.Orders.pick_random()
		menu_i_want.append(random_order)
	return true



func _on_impatient_counter_timeout() -> void: 
	#TODO if impatient counter goes 0 = player lost life and current customer left out of pool
	Global.player_health -= 1
	if not menu_i_want.is_empty():
		menu_i_want = []
	else: self.queue_free()

The only code that normally touched customer_inparty are Table and Spawn(it’s shared entire scene) script itself

EDIT 2: I’ve decied to post requested print first as i’m actually gonna take carefully look into queue_freed() line in Table itself as i think i might have do it horribly and it’s trying to access freed memory like what you stated before


(Sorry for horribly formatted image :folded_hands:)

EDIT 1: I’m currently looking into customer taken order queue freed issues, might take a while to post print result

I think the issue is that you’re using the same Customer scene for both the party and the individual customers.

var CUS = CUSTOMER.instantiate() # party

for i in customer_generated_amount:
	var customer = CUSTOMER.instantiate() # individual
	CUS.customer_inparty.append(customer)

Every individual customer also has its own:

var customer_inparty: Array = []

so their arrays are naturally empty.

You should think about having separate concepts/classes:

  • CustomerParty
    • Customer
    • Customer
    • Customer

The CustomerParty owns customer_inparty; an individual Customer should not.

I tried to fix by seperating into two (Customer and Individual_Customer)

CustomerParty

extends CharacterBody2D

var has_ordered: bool = false
var inside_table:bool = false
var customer_inparty:Array = []


const NPC_1 = preload("uid://dvx33pc5j0y18")
const NPC_2 = preload("uid://c1d2vvqey7uai")
const NPC_6 = preload("uid://dlsng01br1f6m")

func _ready() -> void: 
	if len(customer_inparty) >= 3 and len(customer_inparty) <= 6:
		$Sprite2D.texture = NPC_6
		print_debug("Big generation")
	elif len(customer_inparty) > 1:
		$Sprite2D.texture = NPC_2
		print_debug("mid generation")
	elif len(customer_inparty) <= 1: 
		$Sprite2D.texture = NPC_1
		print_debug("small generation")
	#TODO the moment customer entered the shop, the timer started
	$ProgressBar.max_value = $Impatient_counter.wait_time
	$Impatient_counter.start()
	print_debug(customer_inparty)
	
func _process(delta: float) -> void:
	$ProgressBar.value = $Impatient_counter.time_left


func _on_impatient_counter_timeout() -> void: 
	#TODO if impatient counter goes 0 = player lost life and current customer left out of pool
	Global.player_health -= 1
	for i in customer_inparty:
		i.queue_free()
	self.queue_free()

Individual Customer

extends CharacterBody2D

var menu_i_want:Array #TODO generated into array
@onready var limit_i_can: int = 1 #rng.randi_range(1,3)
var has_ordered: bool = false
var inside_table:bool = false
var result_string: String
var order_recieved: bool = false

const NPC_1 = preload("uid://dvx33pc5j0y18")


func _ready() -> void: 
	#self modulate depends on amount of customer in party
	$Sprite2D.texture = NPC_1
	#TODO the moment customer entered the shop, the timer started
	$ProgressBar.max_value = $Impatient_counter.wait_time
	$Impatient_counter.start()
	#print_debug(customer_inparty)
	
func _process(delta: float) -> void:
	$ProgressBar.value = $Impatient_counter.time_left
	if inside_table and not has_ordered and menu_i_want.is_empty():
		$Panel.visible = true
		$"Customer area".monitorable = false
		$"Customer area".monitoring = false
		$"Customer area/CollisionShape2D".disabled = true
		random_gen_menu()
		print_debug("I ordered")
		has_ordered = true
		menu_gen_text()
		$Panel/Label.text = result_string

	if inside_table and has_ordered and menu_i_want.is_empty():
		order_recieved = true
		$Impatient_counter.stop()
		$Panel.visible = false

func menu_gen_text():
	for item in menu_i_want:
			result_string += str(item.name)

func random_gen_menu():
	print_debug(limit_i_can)
	for i in limit_i_can:
		var random_order = Global.Orders.pick_random()
		menu_i_want.append(random_order)
	return true



func _on_impatient_counter_timeout() -> void: 
	#TODO if impatient counter goes 0 = player lost life and current customer left out of pool
	Global.player_health -= 1
	if not menu_i_want.is_empty():
		menu_i_want = []
	else: self.queue_free()

(Happy to said the bug seem? to occured less) But it is still happened sometimes and fixing itself sometime again

And i slighty change my queue_freed to see if it’s helping (Happy to say it does not)

extends Control

var table_isavaliable: bool = true
var customer_inchairtaken: Array = [] #ADDED Use for clearing customer out
var customers_taken: Dictionary = {}
enum TYPES {Small,Mid,Big}
@export var table_type: TYPES
@onready var chair_location = $Chair_avaliable.get_children()
@onready var number_ofchairs =  len($Chair_avaliable.get_children())
enum STATES {Freed, Taken}
var index:int = 0
var amount_ofcustomer:int = 0
var amount_ofcustomertaken:int = 0

func _ready() -> void:
	checking_orders()

func _process(delta: float) -> void:
	if not customers_taken.is_empty():
		checking_orders()
		if amount_ofcustomertaken == amount_ofcustomer:
			for i in customer_inchairtaken: #CHANGED
				if is_instance_valid(i): #ADDED
					Global.current_customer_inscene -= 1
					print_debug("Customer in party in table cleared",customers_taken) 
					i.queue_free()
			customers_taken = {}
			table_isavaliable = true

(post deleted by author)

I may not have fully understood what your problem is.

Can you explain to me what you expect to happen and what is actually happening?

Basically there’s supposed to be Individual_Customers(Instantiate Scene) that’s supposed to stored them into customer_inparty(Array) inside CustomerParty


But it’s sometimes disappeared or removed out of Array (I ran thro every single of my classes to see what’s removing them and found none) and sometimes it’s correctly presented in Array

I’m not sure what’s exactly causing the bug in this (As i have ran every possible solutions i have in my head)

Hmm, well the first thing that screams out to me is that you have both classes handling _on_impatient_counter_timeout().

  • When the CustomerParty’s timer ends, you remove everyone in the party and then remove itself (note: I would likely lean towards having CustomerParty be a manager of sorts, and it be a Node2D/Node3D, that way you can add the party member’s as children and freeing itself will also free the party members)
  • The IndividualCustomer frees itself if menu_i_want is not empty.

EDIT: I just re-read and noticed that the debugging is in character.gd and at the ready function.

So are you saying that when a new party of customers spawn, only the first party has 1 party member?

I’m also seeing that you are sharing debugging code from two different scripts. One is character.gd and another is something like player.gd and from a detection function. I’m not sure if you have explained the actual issue correctly here.