Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ports extensions from Nebula #9059

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions code/_helpers/unsorted.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1595,3 +1595,7 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
/proc/CallAsync(datum/source, proctype, list/arguments)
set waitfor = FALSE
return call(source, proctype)(arglist(arguments))

// call to generate a stack trace and print to runtime logs
/proc/get_stack_trace(msg, file, line)
CRASH("%% [file],[line] %% [msg]")
2 changes: 2 additions & 0 deletions code/_macros.dm
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@

//check if all bitflags specified are present
#define CHECK_MULTIPLE_BITFIELDS(flagvar, flags) ((flagvar & (flags)) == flags)

#define PRINT_STACK_TRACE(X) get_stack_trace(X, __FILE__, __LINE__)
2 changes: 2 additions & 0 deletions code/datums/extensions/_defines.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#define EXTENSION_FLAG_NONE 0
#define EXTENSION_FLAG_IMMEDIATE 1 // Instantly instantiates, instead of doing it lazily.
26 changes: 26 additions & 0 deletions code/datums/extensions/event_registration.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// For registering for events to be called when certain conditions are met.

/datum/extension/event_registration
base_type = /datum/extension/event_registration
expected_type = /datum
flags = EXTENSION_FLAG_IMMEDIATE
var/decl/observ/event
var/datum/target
var/callproc

/datum/extension/event_registration/New(datum/holder, decl/observ/event, datum/target, callproc)
..()
event.register(target, src, .proc/trigger)
GLOB.destroyed_event.register(target, src, .proc/qdel_self)

src.event = event
src.target = target
src.callproc = callproc

/datum/extension/event_registration/Destroy()
GLOB.destroyed_event.unregister(target, src, .proc/qdel_self)
event.unregister(target, src)
. = ..()

/datum/extension/event_registration/proc/trigger()
call(holder, callproc)(arglist(args))
77 changes: 77 additions & 0 deletions code/datums/extensions/extensions.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/datum/extension
var/base_type
var/datum/holder = null // The holder
var/expected_type = /datum
var/flags = EXTENSION_FLAG_NONE

/datum/extension/New(var/datum/holder)
if(!istype(holder, expected_type))
CRASH("Invalid holder type. Expected [expected_type], was [holder.type]")
src.holder = holder

/datum/extension/proc/post_construction()

/datum/extension/Destroy()
holder = null
. = ..()

/datum
var/list/datum/extension/extensions

//Variadic - Additional positional arguments can be given. Named arguments might not work so well
/proc/set_extension(var/datum/source, var/datum/extension/extension_type)
var/datum/extension/extension_base_type = initial(extension_type.base_type)
if(!ispath(extension_base_type, /datum/extension))
CRASH("Invalid base type: Expected /datum/extension, was [log_info_line(extension_base_type)]")
if(!ispath(extension_type, extension_base_type))
CRASH("Invalid extension type: Expected [extension_base_type], was [log_info_line(extension_type)]")
if(!source.extensions)
source.extensions = list()
var/datum/extension/existing_extension = source.extensions[extension_base_type]
if(istype(existing_extension))
qdel(existing_extension)

if(initial(extension_base_type.flags) & EXTENSION_FLAG_IMMEDIATE)
var/datum/extension/created = construct_extension_instance(extension_type, source, args.Copy(3))
source.extensions[extension_base_type] = created
created.post_construction()
return created

var/list/extension_data = list(extension_type, source)
if(args.len > 2)
extension_data += args.Copy(3)
source.extensions[extension_base_type] = extension_data

/proc/get_or_create_extension(var/datum/source, var/datum/extension/extension_type)
var/base_type = initial(extension_type.base_type)
if(!has_extension(source, base_type))
set_extension(arglist(args))
return get_extension(source, base_type)

/proc/get_extension(var/datum/source, var/base_type)
if(!source.extensions)
return
. = source.extensions[base_type]
if(!.)
return
if(islist(.)) //a list, so it's expecting to be lazy-loaded
var/list/extension_data = .
var/datum/extension/created = construct_extension_instance(extension_data[1], extension_data[2], extension_data.Copy(3))
source.extensions[base_type] = created
created.post_construction()
return created

//Fast way to check if it has an extension, also doesn't trigger instantiation of lazy loaded extensions
/proc/has_extension(var/datum/source, var/base_type)
return !!(source.extensions && source.extensions[base_type])

/proc/construct_extension_instance(var/extension_type, var/datum/source, var/list/arguments)
arguments = list(source) + arguments
return new extension_type(arglist(arguments))

/proc/remove_extension(var/datum/source, var/base_type)
if(!source.extensions || !source.extensions[base_type])
return
if(!islist(source.extensions[base_type]))
qdel(source.extensions[base_type])
LAZYREMOVE(source.extensions, base_type)
36 changes: 36 additions & 0 deletions code/datums/extensions/interactive.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//Extensions that can be interacted with via Topic
/datum/extension/interactive
base_type = /datum/extension/interactive
var/list/host_predicates
var/list/user_predicates

/datum/extension/interactive/New(var/datum/holder, var/host_predicates = list(), var/user_predicates = list())
..()

src.host_predicates = host_predicates ? host_predicates : list()
src.user_predicates = user_predicates ? user_predicates : list()

/datum/extension/interactive/Destroy()
host_predicates.Cut()
user_predicates.Cut()
return ..()

/datum/extension/interactive/proc/extension_status(var/mob/user)
if(!holder || !user)
return STATUS_CLOSE
if(!all_predicates_true(list(holder), host_predicates))
return STATUS_CLOSE
if(!all_predicates_true(list(user), user_predicates))
return STATUS_CLOSE
if(holder.CanUseTopic(user, global.default_topic_state) != STATUS_INTERACTIVE)
return STATUS_CLOSE

return STATUS_INTERACTIVE

/datum/extension/interactive/proc/extension_act(var/href, var/list/href_list, var/mob/user)
return extension_status(user) == STATUS_CLOSE

/datum/extension/interactive/Topic(var/href, var/list/href_list)
if(..())
return TRUE
return extension_act(href, href_list, usr)
112 changes: 112 additions & 0 deletions code/datums/extensions/label.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/datum/extension/labels
base_type = /datum/extension/labels
expected_type = /atom
var/atom/atom_holder
var/list/labels

/datum/extension/labels/New()
..()
atom_holder = holder

/datum/extension/labels/Destroy()
atom_holder = null
return ..()

/datum/extension/labels/proc/AttachLabel(var/mob/user, var/label)
if(!CanAttachLabel(user, label))
return

if(!LAZYLEN(labels))
atom_holder.verbs += /atom/proc/RemoveLabel
LAZYADD(labels, label)

if(user)
user.visible_message("<span class='notice'>\The [user] attaches a label to \the [atom_holder].</span>", \
"<span class='notice'>You attach a label, '[label]', to \the [atom_holder].</span>")

var/old_name = atom_holder.name
atom_holder.name = "[atom_holder.name] ([label])"
var/decl/observ/name_set/N = GET_DECL(/decl/observ/name_set)
N.raise_event(src, old_name, atom_holder.name)
return TRUE

/datum/extension/labels/proc/RemoveLabel(var/mob/user, var/label)
if(!(label in labels))
return

LAZYREMOVE(labels, label)
if(!LAZYLEN(labels))
atom_holder.verbs -= /atom/proc/RemoveLabel

var/full_label = " ([label])"
var/index = findtextEx(atom_holder.name, full_label)
if(!index) // Playing it safe, something might not have set the name properly
return

if(user)
user.visible_message("<span class='notice'>\The [user] removes a label from \the [atom_holder].</span>", \
"<span class='notice'>You remove a label, '[label]', from \the [atom_holder].</span>")

var/old_name = atom_holder.name
// We find and replace the first instance, since that's the one we removed from the list
atom_holder.name = replacetext(atom_holder.name, full_label, "", index, index + length(full_label))
var/decl/observ/name_set/N = GET_DECL(/decl/observ/name_set)
N.raise_event(src, old_name, atom_holder.name)
return TRUE

/datum/extension/labels/proc/RemoveAllLabels()
. = TRUE
for(var/lbl in labels)
if(!RemoveLabel(null, lbl))
. = FALSE

// We may have to do something more complex here
// in case something appends strings to something that's labelled rather than replace the name outright
// Non-printable characters should be of help if this comes up
/datum/extension/labels/proc/AppendLabelsToName(var/name)
if(!LAZYLEN(labels))
return name
. = list(name)
for(var/entry in labels)
. += " ([entry])"
. = jointext(., null)

/datum/extension/labels/proc/CanAttachLabel(var/user, var/label)
if(!length(label))
return FALSE
if(ExcessLabelLength(label, user))
return FALSE
return TRUE

/datum/extension/labels/proc/ExcessLabelLength(var/label, var/user)
. = length(label) + 3 // Each label also adds a space and two brackets when applied to a name
if(LAZYLEN(labels))
for(var/entry in labels)
. += length(entry) + 3
. = . > 64 ? TRUE : FALSE
if(. && user)
to_chat(user, "<span class='warning'>The label won't fit.</span>")

/proc/get_attached_labels(var/atom/source)
if(has_extension(source, /datum/extension/labels))
var/datum/extension/labels/L = get_extension(source, /datum/extension/labels)
if(LAZYLEN(L.labels))
return L.labels.Copy()
return list()

/atom/proc/RemoveLabel(var/label in get_attached_labels(src))
set name = "Remove Label"
set desc = "Used to remove labels"
set category = "Object"
set src in view(1)

if(Adjacent(usr))
if(has_extension(src, /datum/extension/labels))
var/datum/extension/labels/L = get_extension(src, /datum/extension/labels)
L.RemoveLabel(usr, label)

//Single label allowed for this one
/datum/extension/labels/single/CanAttachLabel(user, label)
if(LAZYLEN(labels) >= 1) //Only allow a single label
return FALSE
. = ..()
28 changes: 28 additions & 0 deletions code/datums/observation/name_set.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Observer Pattern Implementation: Name Set
// Registration type: /atom
//
// Raised when: An atom's name changes.
//
// Arguments that the called proc should expect:
// /atom/namee: The atom that had its name set
// /old_name: name before the change
// /new_name: name after the change

/decl/observ/name_set
name = "Name Set"
expected_type = /atom

/*********************
* Name Set Handling *
*********************/

/atom/proc/SetName(var/new_name)
var/old_name = name
if(old_name != new_name)
name = new_name
if(has_extension(src, /datum/extension/labels))
var/datum/extension/labels/L = get_extension(src, /datum/extension/labels)
name = L.AppendLabelsToName(name)

var/decl/observ/name_set/N = GET_DECL(/decl/observ/name_set)
N.raise_event(src, old_name, name)
30 changes: 30 additions & 0 deletions code/datums/state_machine/state.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// An individual state, defined as a `/decl` to save memory.
// On a directed graph, these would be the nodes themselves, connected to each other by unidirectional arrows.
/decl/state
// Transition decl types, which get turned into refs to those types.
// Note that the order DOES matter, as decls earlier in the list have higher priority
// if more than one becomes 'open'.
var/list/transitions = null

/decl/state/Initialize()
. = ..()
for(var/i in 1 to LAZYLEN(transitions))
var/decl/state_transition/T = GET_DECL(transitions[i])
T.from += src
transitions[i] = T

// Returns a list of transitions that a FSM could switch to.
// Note that `holder` is NOT the FSM, but instead the thing the FSM is attached to.
/decl/state/proc/get_open_transitions(datum/holder)
for(var/decl/state_transition/T as anything in transitions)
if(T.is_open(holder))
LAZYADD(., T)

// Stub for child states to modify the holder when switched to.
// Again, `holder` is not the FSM.
/decl/state/proc/entered_state(datum/holder)
return

// Another stub for when leaving a state.
/decl/state/proc/exited_state(datum/holder)
return
16 changes: 16 additions & 0 deletions code/datums/state_machine/transition.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Used to connect `/decl/state`s together so the FSM knows what state to switch to, and on what conditions.
// On a directed graph, these would be the arrows connecting the nodes representing states.
/decl/state_transition
var/list/from = null
var/decl/state/target = null

// Called by one or more state decls acting as nodes in a directed graph.
/decl/state_transition/Initialize()
. = ..()
LAZYINITLIST(from)
if(ispath(target))
target = GET_DECL(target)

// Tells the FSM if it should or should not be allowed to transfer to the target state.
/decl/state_transition/proc/is_open(datum/holder)
return FALSE
5 changes: 3 additions & 2 deletions code/modules/mob/living/silicon/silicon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@
idcard = new idcard_type(src)
set_id_info(idcard)

/mob/living/silicon/proc/SetName(pickedName as text)
real_name = pickedName
/mob/living/silicon/SetName(new_name as text)
real_name = new_name
name = real_name
..()

/mob/living/silicon/proc/show_laws()
return
Expand Down
Loading