Files
2026-01-26 10:51:59 -08:00

266 lines
9.6 KiB
Plaintext

//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
/**
* /obj/item's that fit a filter
*/
/datum/bulk_entity_persistence/trash
id = "trash"
/datum/bulk_entity_persistence/trash/is_enabled()
return CONFIG_GET(flag/persistent_trash)
/datum/bulk_entity_persistence/trash/gather_all()
. = list()
for(var/obj/item/item in world)
if(!isturf(item.loc))
continue
. += item
return filter_items(.)
/datum/bulk_entity_persistence/trash/gather_level(z)
. = list()
for(var/obj/item/item in world)
if(item.z != z)
continue
. += item
return filter_items(.)
/datum/bulk_entity_persistence/trash/perform_global_filter(list/atom/movable/entities)
entities = SSpersistence.entity_filter_out_non_persisting_objs(entities)
entities = SSpersistence.entity_filter_out_eroding_turfs(entities)
return entities
/datum/bulk_entity_persistence/trash/perform_level_filter(list/atom/movable/entities, datum/map_level/level)
// yeah the voronoi algorithm doesn't work if there's nothing to compute tbh
// we need atleast 1 for proper triangulation, but let's be safe and break if not 3+.
if(length(entities) <= 3)
return entities
// no more trash islands :(
var/list/bucket = list()
for(var/i in 1 to length(entities))
if (prob(50))
continue
bucket += entities[i]
return bucket
// perform entity filtering based on level and configuraiton
// var/mesh_heuristic = level.persistent_trash_mesh_heuristic + round(length(entities) * 0.001 * level.persistent_trash_mesh_heuristic_escalate_per_thousand)
// var/drop_n_largest_meshes = level.persistent_trash_drop_n_largest
// var/drop_n_smallest_meshes = level.persistent_trash_drop_n_smallest
// var/drop_n_most_isolated = level.persistent_trash_drop_n_most_isolated
// var/drop_n_least_isolated = level.persistent_trash_drop_n_least_isolated
// // run heuristics
// var/list/results = heuristics(entities, mesh_heuristic)
// var/list/meshes = results[1]
// var/list/singles = results[2]
// // sort results by descending density
// tim_sort(meshes, GLOBAL_PROC_REF(cmp_persistent_trash_group_density_dsc))
// tim_sort(singles, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
// // drop results as needed
// meshes.Cut(1, min(length(meshes) + 1, drop_n_largest_meshes + 1))
// meshes.len -= drop_n_smallest_meshes
// singles.Cut(1, min(length(singles) + 1, drop_n_least_isolated + 1))
// singles.len -= drop_n_most_isolated
// // collate filtered back into entities
// var/list/collating = singles.Copy()
// for(var/datum/persistent_trash_group/mesh as anything in meshes)
// collating += mesh.trash
// return collating
/datum/bulk_entity_persistence/trash/serialize_entities_into_chunks(list/atom/movable/entities, datum/map_level/level, datum/map_level_persistence/persistence)
var/list/datum/bulk_entity_chunk/chunks = list()
if(!length(entities))
return
// split by area/turf
var/list/area_turf_tuples = SSpersistence.entity_group_by_area_and_turf(entities)
for(var/list/area_turf_tuple as anything in area_turf_tuples)
var/area_type = area_turf_tuple[1]
var/turf_type = area_turf_tuple[2]
var/list/area_turf_entities = area_turf_tuples[area_turf_tuple]
// limit to 500 entities per chunk
for(var/list/atom/movable/chunk_entities as anything in SSpersistence.entity_split_by_amount(area_turf_entities, 500))
// create chunk
var/datum/bulk_entity_chunk/chunk = new
chunk.level_id = persistence.level_id
var/list/entities_constructed = list()
for(var/atom/movable/entity as anything in chunk_entities)
entities_constructed[++entities_constructed.len] = list(
"x" = entity.x,
"y" = entity.y,
"type" = entity.type,
"data" = entity.serialize(),
)
chunk.data = list(
"area_lock" = area_type,
"turf_lock" = turf_type,
"entities" = entities_constructed,
)
chunks += chunk
return chunks
/datum/bulk_entity_persistence/trash/load_chunks(list/datum/bulk_entity_chunk/chunks)
var/loaded = 0
var/dropped = 0
for(var/datum/bulk_entity_chunk/chunk as anything in chunks)
var/z_index = SSmapping.keyed_levels[chunk.level_id]?.z_index
var/rounds_since = chunk.rounds_since_saved
var/hours_since = chunk.hours_since_saved
// drop if level not loaded
if(isnull(z_index))
dropped++
continue
var/area_type = text2path(chunk.data["area_lock"])
var/turf_type = text2path(chunk.data["turf_lock"])
// drop if area/turf types no longer exist
if(!ispath(area_type))
dropped++
continue
if(!ispath(turf_type))
dropped++
continue
// entities = list(list("type" = type, "data" = list), ...)
var/list/entities = chunk.data["entities"]
for(var/list/struct as anything in entities)
if(!islist(struct))
continue
var/entity_type = text2path(struct["type"])
var/list/entity_data = struct["data"]
// check for valid item
if(!ispath(entity_type, /obj/item))
dropped++
continue
var/x = struct["x"]
var/y = struct["y"]
// locate spawn location
var/turf/where = locate(x, y, z_index)
if(isnull(where))
dropped++
continue
// check area / turf constraint
if(!istype(where, turf_type) || !istype(where.loc, area_type))
dropped++
continue
var/obj/item/creating = new entity_type(where)
creating.deserialize(entity_data)
creating.decay_persisted(rounds_since, hours_since)
loaded++
return list(loaded, dropped, 0)
/datum/bulk_entity_persistence/trash/proc/trash_typecache()
var/list/umbrella_inclusions = list(
/obj/item/trash,
)
var/list/specific_inclusions = list(
/obj/item/cigbutt,
/obj/item/paper/crumpled,
)
for(var/type in specific_inclusions)
specific_inclusions[type] = TRUE
return typecacheof(umbrella_inclusions) | specific_inclusions
/datum/bulk_entity_persistence/trash/proc/filter_items(list/obj/item/items)
// append, not remove; save the vector resizing/etc
// we could swap with end instead but eh.
var/list/filtered = list()
var/list/typecache = trash_typecache()
for(var/obj/item/item as anything in items)
// first, check typecache
if(!typecache[item.type])
continue
filtered += item
return filtered
/**
* all items should be on the same zlevel.
*
* returns a list with indices:
*
* 1: meshes with densities
* 2: list of single items associated to density
*
* @return list(list(/datum/persistent_trash_group instance, ..), list(item instance = density, ...))
*/
// /datum/bulk_entity_persistence/trash/proc/heuristics(list/obj/item/items, mesh_heuristic_threshold)
// // we need a way to dedupe, so...
// // this is the 'real' graph vertices list; only one is made for a coordinate.
// // this is associated to the list of items in it.
// var/list/datum/vec2/vertices = list()
// // lockstepped list of vec2's
// var/list/datum/vec2/lockstepped_points = list()
// // 16 MB at 1000x1000, dropped right after
// var/list/buffer = new /list(world.maxx * world.maxy)
// for(var/i in 1 to length(items))
// var/obj/item/item = items[i]
// var/index = (item.y - 1) * world.maxx + (item.x)
// if(isnull(buffer[index]))
// var/datum/vec2/created_point = new /datum/vec2(item.x, item.y)
// buffer[index] = created_point
// vertices[created_point] = list()
// vertices[buffer[index]] += item
// lockstepped_points += buffer[index]
// // drop buffer now
// buffer = null
// // enough?
// if(length(vertices) < 3)
// // if less than 3 vertices they're all singles
// // todo: we should try to mesh anything on the same tile..
// return list(list(), items)
// // construct graph
// var/datum/graph/constructed_graph = vec2_dual_delaunay_voronoi_graph(vertices)
// // prepare
// var/list/datum/persistent_trash_group/meshes = list()
// var/list/obj/item/singles = list()
// var/list/datum/vec2/vertices_processing = vertices.Copy()
// var/list/datum/vec2/processed = list()
// // go through vertices and create meshes
// while(length(vertices_processing))
// var/datum/vec2/vertex = vertices_processing[length(vertices_processing)]
// vertices_processing.len--
// var/list/items_on_vertex = vertices[vertex]
// var/list/datum/vec2/expanding = list(vertex)
// var/list/obj/item/items_within_group = list()
// items_within_group += items_on_vertex
// var/maximum_density_over_expansion = 0
// var/expanding_index = 1
// while(expanding_index <= length(expanding))
// var/datum/vec2/source = expanding[expanding_index]
// expanding_index++
// if(processed[source])
// continue
// processed[source] = TRUE
// maximum_density_over_expansion = max(maximum_density_over_expansion, 1 / source.voronoi_area)
// for(var/datum/vec2/dest in constructed_graph.vertices[source])
// if(source.chebyshev_distance_to(dest) > mesh_heuristic_threshold)
// continue
// expanding |= dest
// items_within_group += vertices[source]
// vertices_processing -= dest
// if(length(items_within_group) > 1)
// var/datum/persistent_trash_group/constructed_mesh = new
// constructed_mesh.trash = items_within_group
// constructed_mesh.highest_marked_density = maximum_density_over_expansion
// // todo: configurable?
// var/density_escalation_heuristic = length(items_within_group) / 20
// constructed_mesh.highest_marked_density *= density_escalation_heuristic
// meshes += constructed_mesh
// else
// ASSERT(items_within_group[1])
// singles += items_within_group[1]
// return list(meshes, singles)
/datum/persistent_trash_group
/// trash in this group
var/list/obj/item/trash
/// highest marked density in this group
var/highest_marked_density = 0
/proc/cmp_persistent_trash_group_density_dsc(datum/persistent_trash_group/A, datum/persistent_trash_group/B)
return B.highest_marked_density - A.highest_marked_density