## Issues bounded, per-frame permits for renderer main-thread operation lanes.
##
## The scheduler owns counters only. Queue contents, operation ordering and the
## side effects performed after a permit remain owned by the caller. One instance
## belongs to one renderer lifecycle and must only be used on the main thread.
class_name RenderBudgetScheduler
extends RefCounted

const TILE_FINALIZE: StringName = &"tile_finalize"
const TERRAIN_UPGRADE_FINALIZE: StringName = &"terrain_upgrade_finalize"
const TERRAIN_CONTROL_SPLAT_FINALIZE: StringName = &"terrain_control_splat_finalize"
const TERRAIN_SPLAT_CACHE_FINALIZE: StringName = &"terrain_splat_cache_finalize"
const TERRAIN_SPLAT_BUILD: StringName = &"terrain_splat_build"
const WATER_FINALIZE: StringName = &"water_finalize"
const CHUNK_GEOMETRY: StringName = &"chunk_geometry"
const TILE_LOAD_START: StringName = &"tile_load_start"
const TILE_LOD_CREATE: StringName = &"tile_lod_create"
const TILE_LOD_REMOVE: StringName = &"tile_lod_remove"
const M2_ANIMATION_FINALIZE: StringName = &"m2_animation_finalize"
const M2_MESH_FINALIZE: StringName = &"m2_mesh_finalize"
const M2_BUILD: StringName = &"m2_build"
const WMO_BUILD: StringName = &"wmo_build"
const WMO_RENDER_GROUP_BUILD: StringName = &"wmo_render_group_build"
const DETAIL_ASSET_SYNC: StringName = &"detail_asset_sync"

var _remaining_permits_by_lane: Dictionary = {}
var _consumed_permits_by_lane: Dictionary = {}
var _is_cancelled := false


## Replaces all lane limits for a new frame. Negative limits are treated as zero.
## Calling this after [method cancel] leaves the scheduler cancelled.
func begin_frame(operation_limits_by_lane: Dictionary) -> void:
	_remaining_permits_by_lane.clear()
	_consumed_permits_by_lane.clear()
	if _is_cancelled:
		return

	for lane_variant in operation_limits_by_lane:
		var lane := StringName(lane_variant)
		_remaining_permits_by_lane[lane] = maxi(0, int(operation_limits_by_lane[lane_variant]))
		_consumed_permits_by_lane[lane] = 0


## Returns whether a lane can issue another permit without consuming it.
## Unknown lanes and cancelled schedulers return false.
func has_remaining_permit(lane: StringName) -> bool:
	if _is_cancelled:
		return false
	return int(_remaining_permits_by_lane.get(lane, 0)) > 0


## Consumes one permit from [param lane] and returns true when it was granted.
## Unknown, exhausted or cancelled lanes return false without changing state.
func try_consume_permit(lane: StringName) -> bool:
	if not has_remaining_permit(lane):
		return false
	_remaining_permits_by_lane[lane] = int(_remaining_permits_by_lane[lane]) - 1
	_consumed_permits_by_lane[lane] = int(_consumed_permits_by_lane.get(lane, 0)) + 1
	return true


## Returns the unconsumed permit count for one lane.
func remaining_permits(lane: StringName) -> int:
	if _is_cancelled:
		return 0
	return int(_remaining_permits_by_lane.get(lane, 0))


## Returns a detached diagnostic snapshot of permits consumed in this frame.
func consumed_permits_snapshot() -> Dictionary:
	return _consumed_permits_by_lane.duplicate()


## Permanently rejects new permits for this scheduler instance and clears limits.
## In-flight work remains the caller's responsibility and is not interrupted.
func cancel() -> void:
	_is_cancelled = true
	_remaining_permits_by_lane.clear()


## Returns whether [method cancel] ended this scheduler lifecycle.
func is_cancelled() -> bool:
	return _is_cancelled
