diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 046425653..48ae6109c 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -247,8 +247,9 @@ void VMethodInterposeLinkBase::set_chain(void *chain) addr_to_method_pointer_(chain_mptr, chain); } -VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr) - : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr), +VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority) + : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), + chain_mptr(chain_mptr), priority(priority), applied(false), saved_chain(NULL), next(NULL), prev(NULL) { if (vmethod_idx < 0 || interpose_method == NULL) @@ -349,15 +350,26 @@ bool VMethodInterposeLinkBase::apply(bool enable) return false; // Retrieve the current vtable entry - void *old_ptr = host->get_vmethod_ptr(vmethod_idx); VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; + VMethodInterposeLinkBase *next_link = NULL; + while (old_link && old_link->host == host && old_link->priority > priority) + { + next_link = old_link; + old_link = old_link->prev; + } + + void *old_ptr = next_link ? next_link->saved_chain : host->get_vmethod_ptr(vmethod_idx); assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); // Apply the new method ptr set_chain(old_ptr); - if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) + if (next_link) + { + next_link->set_chain(interpose_method); + } + else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) { set_chain(NULL); return false; @@ -365,8 +377,13 @@ bool VMethodInterposeLinkBase::apply(bool enable) // Push the current link into the home host applied = true; - host->interpose_list[vmethod_idx] = this; prev = old_link; + next = next_link; + + if (next_link) + next_link->prev = this; + else + host->interpose_list[vmethod_idx] = this; child_hosts.clear(); child_next.clear(); @@ -374,13 +391,22 @@ bool VMethodInterposeLinkBase::apply(bool enable) if (old_link && old_link->host == host) { // If the old link is home, just push into the plain chain - assert(old_link->next == NULL); + assert(old_link->next == next_link); old_link->next = this; // Child links belong to the topmost local entry child_hosts.swap(old_link->child_hosts); child_next.swap(old_link->child_next); } + else if (next_link) + { + if (old_link) + { + assert(old_link->child_next.count(next_link)); + old_link->child_next.erase(next_link); + old_link->child_next.insert(this); + } + } else { // If creating a new local chain, find children with same vmethod @@ -401,6 +427,8 @@ bool VMethodInterposeLinkBase::apply(bool enable) } } + assert (!next_link || (child_next.empty() && child_hosts.empty())); + // Chain subclass hooks for (auto it = child_next.begin(); it != child_next.end(); ++it) { diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index 7ba6b67aa..aeb407a8c 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -87,7 +87,8 @@ namespace DFHack with code defined by DFHack, while retaining ability to call the original code. The API can be safely used from plugins, and multiple hooks for the same vmethod are - automatically chained in undefined order. + automatically chained (subclass before superclass; at same + level highest priority called first; undefined order otherwise). Usage: @@ -105,6 +106,8 @@ namespace DFHack }; IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); + or + IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority); void init() { if (!INTERPOSE_HOOK(my_hack, foo).apply()) @@ -121,9 +124,11 @@ namespace DFHack static DFHack::VMethodInterposeLink interpose_##name; \ rtype interpose_fn_##name args -#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \ +#define IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,priority) \ DFHack::VMethodInterposeLink \ - class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name); + class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority); + +#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,0) #define INTERPOSE_NEXT(name) (this->*interpose_##name.chain) #define INTERPOSE_HOOK(class, name) (class::interpose_##name) @@ -140,6 +145,7 @@ namespace DFHack int vmethod_idx; void *interpose_method; // Pointer to the code of the interposing method void *chain_mptr; // Pointer to the chain field below + int priority; bool applied; void *saved_chain; // Previous pointer to the code @@ -155,7 +161,7 @@ namespace DFHack VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); void find_child_hosts(virtual_identity *cur, void *vmptr); public: - VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr); + VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority); ~VMethodInterposeLinkBase(); bool is_applied() { return applied; } @@ -171,12 +177,13 @@ namespace DFHack operator Ptr () { return chain; } template - VMethodInterposeLink(Ptr target, Ptr2 src) + VMethodInterposeLink(Ptr target, Ptr2 src, int priority) : VMethodInterposeLinkBase( &Base::_identity, vmethod_pointer_to_idx(target), method_pointer_to_addr(src), - &chain + &chain, + priority ) { src = target; /* check compatibility */ } }; diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 059a4be62..ecebbb90c 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -130,7 +130,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) INTERPOSE_NEXT(getName)(buf); \ } \ }; \ - IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); + IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cname##_hook, getName, 100); KNOWN_BUILDINGS #undef BUILDING