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

Fix ScriptHook_t initialization order #320

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

z33ky
Copy link

@z33ky z33ky commented Sep 5, 2024

When a ScriptClassDesc_t for is initialized (SCRIPTDESC), it recursively invokes its parents initializers in order to obtain their pHelper member.
Initialization is only done once, so repeated initialization is skipped. Initialization includes assignment of a vector of ScriptHook_t's (DEFINE_SCRIPTFUNC/BEGIN_SCRIPTHOOK), which must be initialized beforehand.
Both of these use (static) globals; Within a translation unit, initialization order is defined to be the same as the order of declaration. So within a translation unit we must define all ScriptHook_t's before the ScriptClassDesc_t using them. A problem occurs with the parent initialization though, since there is no defined order between translation units, meaning initialization of a ScriptClassDesc_t can happen before its ScriptHook_t's, despite being the correct order within its translation unit.

On MSVC it seems this issue is benign. On GCC/Linux however the initialization of a ScriptHook_t essentially cleared whatever happened during the initialization of the ScriptClassDesc_t, meaning many hooks simply didn't work.

This situation is remedied by delaying the initialization of the ScriptClassDesc_t's ScriptHook_t vector to only when the constructor of it is invoked from its translation unit. This is accomplished simply by adding a boolean parameter to the function (GetScriptDesc()) that is true in the global constructor invocation, and false by default (including when doing parent ScriptClassDesc_t initialization). When false, a valid ScriptClassDesc_t pointer is still returned, with the proper value for pHelper, which is all that is needed for the initialization of the child ScriptClassDesc_t. The value of the returned pointer is a fixed memory location, and does not change due to the delayed initialization.

Fixes #244.


Does this PR close any issues?

PR Checklist

  • My PR follows all guidelines in the CONTRIBUTING.md file
  • My PR targets a develop branch OR targets another branch with a specific goal in mind

@samisalreadytaken
Copy link

This breaks instance helper fallback assignment in BEGIN_SCRIPTDESC_NAMED for some classes. In MSVC, CBaseAnimating (baseanimating.cpp) won't be assigned a helper.

Test ToString in game with

printl( Entities.CreateByClassname("prop_dynamic") )

It should print ([0] prop_dynamic), but it prints (CBaseAnimating : 0x00)

@z33ky
Copy link
Author

z33ky commented Nov 20, 2024

Hm yeah, I mistook this loop to initialize pHelper completely, but it's actually done through DEFINE_SCRIPT_INSTANCE_HELPER(). It is thus part of the delayed initialization and the loop won't find the proper helper instance.

One solution, which breaks compatibility with upstream vscript though, would be to include the helper as argument to BEGIN_SCRIPTDESC_NAMED(), so it can be initialized eagerly. A compatible, but brittle, solution would be to move the early if (!init) return pDesc; to the script-hook macros, which are the actual ones that must be delayed. We can rely on the optimizer to eliminate the redundant checks for !init. For this to work though, DEFINE_SCRIPT_INSTANCE_HELPER() must be called before the script-hook definitions, lest it silently breaks the helper.
I've gone with the first one for now, since there are relatively few script definitions that define a helper.

When a ScriptClassDesc_t for is initialized (SCRIPTDESC), it recursively
invokes its parents initializers in order to obtain their pHelper
member.
Initialization is only done once, so repeated initialization is skipped.
Initialization includes assignment of a vector of ScriptHook_t's
(DEFINE_SCRIPTFUNC/BEGIN_SCRIPTHOOK), which must be initialized
beforehand.
Both of these use (static) globals; Within a translation unit,
initialization order is defined to be the same as the order of
declaration. So within a translation unit we must define all
ScriptHook_t's before the ScriptClassDesc_t using them.
A problem occurs with the parent initialization though, since there is
no defined order between translation units, meaning initialization of a
ScriptClassDesc_t can happen before its ScriptHook_t's, despite being
the correct order within its translation unit.

On MSVC it seems this issue is benign. On GCC/Linux however the
initialization of a ScriptHook_t essentially cleared whatever happened
during the initialization of the ScriptClassDesc_t, meaning many hooks
simply didn't work.

This situation is remedied by delaying the initialization of the
ScriptClassDesc_t's ScriptHook_t vector to only when the constructor of
it is invoked from its translation unit. This is accomplished simply by
adding a boolean parameter to the function (GetScriptDesc()) that is
true in the global constructor invocation, and false by default
(including when doing parent ScriptClassDesc_t initialization).
When false, a valid ScriptClassDesc_t pointer is still returned, which
is all that is needed for the initialization of the child
ScriptClassDesc_t. The value of the returned pointer is a fixed memory
location, and does not change due to the delayed initialization.
The script-helper must be initialized eagerly though, for the search of
a base-class helper. This also changes the SCRIPTDESC slightly to
accommodate the eager initialization of helper instance pointer.

Fixes entropy-zero#244.
@samisalreadytaken
Copy link

A compatible, but brittle, solution would be to move the early if (!init) return pDesc; to the script-hook macros

I don't think this would be bad. There could even be something like BEGIN_SCRIPTHOOK_DEFINITIONS to indicate the definition section, or just put a note saying hooks should be defined last. Changing script hooks might be preferable over diverging from upstream. I personally don't have a strong preference of one over the other though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants