From a188a8839a6b16bed705d10d9ed47dec50b2f1d2 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 31 Jan 2017 10:09:44 +0100 Subject: [PATCH 01/11] - fixed a typo in Hexen's light definitions. --- wadsrc_lights/static/filter/hexen/gldefs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wadsrc_lights/static/filter/hexen/gldefs.txt b/wadsrc_lights/static/filter/hexen/gldefs.txt index d72b9c76f6..cc9513d978 100644 --- a/wadsrc_lights/static/filter/hexen/gldefs.txt +++ b/wadsrc_lights/static/filter/hexen/gldefs.txt @@ -1345,7 +1345,7 @@ flickerlight2 FIREBULL attenuate 1 color 1.0 0.7 0.0 size 96 - secondarysize - 105 + secondarysize 130 interval 0.1 offset 0 40 0 } From f0a325a904fc8bb2c6ae26cf16cbd52778d75e7a Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 31 Jan 2017 13:22:05 +0100 Subject: [PATCH 02/11] - fixed: The restart CCMD needs to clear the global FraggleScript before taking down the class system. This is a soft root and would never be deleted otherwise. --- src/d_main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/d_main.cpp b/src/d_main.cpp index 6041af9b4d..aa90d6c463 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2707,6 +2707,7 @@ void D_DoomMain (void) S_Shutdown(); // free all channels and delete playlist C_ClearAliases(); // CCMDs won't be reinitialized so these need to be deleted here DestroyCVarsFlagged(CVAR_MOD); // Delete any cvar left by mods + FS_Close(); // destroy the global FraggleScript. GC::FullGC(); // clean up before taking down the object list. From 3131c08640c4e9831cdd9f190bb9b4099ed22b4e Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 31 Jan 2017 13:41:23 +0100 Subject: [PATCH 03/11] - fixed: the restart CCMD must clear out all statically stored pointers to VM functions because they do not survive the shutdown that is needed before loading new data. --- src/dobjtype.cpp | 17 +++++++++++++++++ src/dobjtype.h | 2 ++ src/g_inventory/a_pickups.cpp | 2 +- src/g_statusbar/sbarinfo_commands.cpp | 4 ++-- src/m_cheat.cpp | 2 +- src/p_acs.cpp | 4 ++-- 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index 313ee7389c..7468b6dabb 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -69,6 +69,7 @@ FNamespaceManager Namespaces; FTypeTable TypeTable; TArray PClass::AllClasses; +TArray PClass::FunctionPtrList; bool PClass::bShutdown; bool PClass::bVMOperational; @@ -2840,6 +2841,12 @@ void PClass::StaticShutdown () TArray uniqueFPs(64); unsigned int i, j; + // delete all variables containing pointers to script functions. + for (auto p : FunctionPtrList) + { + *p = nullptr; + } + FunctionPtrList.Clear(); // Make a full garbage collection here so that all destroyed but uncollected higher level objects that still exist can be properly taken down. GC::FullGC(); @@ -3547,6 +3554,16 @@ VMFunction *PClass::FindFunction(FName clsname, FName funcname) return func->Variants[0].Implementation; } +void PClass::FindFunction(VMFunction **pptr, FName clsname, FName funcname) +{ + auto cls = PClass::FindActor(clsname); + if (!cls) return; + auto func = dyn_cast(cls->Symbols.FindSymbol(funcname, true)); + if (!func) return; + *pptr = func->Variants[0].Implementation; + FunctionPtrList.Push(pptr); +} + /* FTypeTable **************************************************************/ diff --git a/src/dobjtype.h b/src/dobjtype.h index 632ef96bbd..2726ebff11 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -848,9 +848,11 @@ public: static PClassActor *FindActor(ENamedName name) { return FindActor(FName(name)); } static PClassActor *FindActor(FName name); static VMFunction *FindFunction(FName cls, FName func); + static void FindFunction(VMFunction **pptr, FName cls, FName func); PClass *FindClassTentative(FName name); static TArray AllClasses; + static TArray FunctionPtrList; static bool bShutdown; static bool bVMOperational; diff --git a/src/g_inventory/a_pickups.cpp b/src/g_inventory/a_pickups.cpp index cd4d02f04a..fc35d2da4a 100644 --- a/src/g_inventory/a_pickups.cpp +++ b/src/g_inventory/a_pickups.cpp @@ -522,7 +522,7 @@ DEFINE_ACTION_FUNCTION(AInventory, DoRespawn) bool AInventory::CallTryPickup(AActor *toucher, AActor **toucher_return) { static VMFunction *func = nullptr; - if (func == nullptr) func = PClass::FindFunction(NAME_Inventory, NAME_CallTryPickup); + if (func == nullptr) PClass::FindFunction(&func, NAME_Inventory, NAME_CallTryPickup); VMValue params[2] = { (DObject*)this, toucher }; VMReturn ret[2]; int res; diff --git a/src/g_statusbar/sbarinfo_commands.cpp b/src/g_statusbar/sbarinfo_commands.cpp index 5e12bf9d05..c3950571f1 100644 --- a/src/g_statusbar/sbarinfo_commands.cpp +++ b/src/g_statusbar/sbarinfo_commands.cpp @@ -1437,7 +1437,7 @@ class CommandDrawNumber : public CommandDrawString { // num = statusBar.CPlayer.mo.GetEffectTicsForItem(inventoryItem) / TICRATE + 1; static VMFunction *func = nullptr; - if (func == nullptr) func = PClass::FindFunction(NAME_PlayerPawn, "GetEffectTicsForItem"); + if (func == nullptr) PClass::FindFunction(&func, NAME_PlayerPawn, "GetEffectTicsForItem"); VMValue params[] = { statusBar->CPlayer->mo, inventoryItem }; int retv; VMReturn ret(&retv); @@ -2830,7 +2830,7 @@ class CommandDrawBar : public SBarInfoCommand // [value, max] = statusBar.CPlayer.mo.GetEffectTicsForItem(inventoryItem); // value++; max++; static VMFunction *func = nullptr; - if (func == nullptr) func = PClass::FindFunction(NAME_PlayerPawn, "GetEffectTicsForItem"); + if (func == nullptr) PClass::FindFunction(&func, NAME_PlayerPawn, "GetEffectTicsForItem"); VMValue params[] = { statusBar->CPlayer->mo, data.inventoryItem }; VMReturn ret[2]; int ival; diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 2f99ea61c1..23c2f1ded7 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -478,7 +478,7 @@ void cht_DoCheat (player_t *player, int cheat) if (player->mo != NULL && player->health >= 0) { static VMFunction *gsp = nullptr; - if (gsp == nullptr) gsp = PClass::FindFunction(NAME_Sigil, NAME_GiveSigilPiece); + if (gsp == nullptr) PClass::FindFunction(&gsp, NAME_Sigil, NAME_GiveSigilPiece); if (gsp) { VMValue params[1] = { player->mo }; diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 24142e0ec5..e346579c4c 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -6203,7 +6203,7 @@ static bool CharArrayParms(int &capacity, int &offset, int &a, int *Stack, int & static void SetMarineWeapon(AActor *marine, int weapon) { static VMFunction *smw = nullptr; - if (smw == nullptr) smw = PClass::FindFunction(NAME_ScriptedMarine, NAME_SetWeapon); + if (smw == nullptr) PClass::FindFunction(&smw, NAME_ScriptedMarine, NAME_SetWeapon); if (smw) { VMValue params[2] = { marine, weapon }; @@ -6214,7 +6214,7 @@ static void SetMarineWeapon(AActor *marine, int weapon) static void SetMarineSprite(AActor *marine, PClassActor *source) { static VMFunction *sms = nullptr; - if (sms == nullptr) sms = PClass::FindFunction(NAME_ScriptedMarine, NAME_SetSprite); + if (sms == nullptr) PClass::FindFunction(&sms, NAME_ScriptedMarine, NAME_SetSprite); if (sms) { VMValue params[2] = { marine, source }; From 94d5d6848e696f45075aafd047fd0ca8a9cea0fe Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 31 Jan 2017 13:46:35 +0100 Subject: [PATCH 04/11] - moved all code that deletes some data from P_SetupLevel to P_FreeLevelData so that P_Shutdown can also call it. This is particularly important for camera textures which otherwise would not be taken down before a restart. --- src/p_setup.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index df41345f42..a4e4ef1b9a 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -3423,6 +3423,17 @@ extern polyblock_t **PolyBlockMap; void P_FreeLevelData () { + MapThingsConverted.Clear(); + MapThingsUserDataIndex.Clear(); + MapThingsUserData.Clear(); + linemap.Clear(); + FCanvasTextureInfo::EmptyList(); + R_FreePastViewers(); + P_ClearUDMFKeys(); + + // [RH] Clear all ThingID hash chains. + AActor::ClearTIDHashes(); + P_FreeMapDataBackup(); interpolator.ClearInterpolations(); // [RH] Nothing to interpolate on a fresh level. Renderer->CleanLevelData(); @@ -3575,14 +3586,6 @@ void P_SetupLevel (const char *lumpname, int position) level.maptype = MAPTYPE_UNKNOWN; wminfo.partime = 180; - MapThingsConverted.Clear(); - MapThingsUserDataIndex.Clear(); - MapThingsUserData.Clear(); - linemap.Clear(); - FCanvasTextureInfo::EmptyList (); - R_FreePastViewers (); - P_ClearUDMFKeys(); - if (!savegamerestore) { for (i = 0; i < MAXPLAYERS; ++i) @@ -3612,8 +3615,6 @@ void P_SetupLevel (const char *lumpname, int position) // Make sure all sounds are stopped before Z_FreeTags. S_Start (); - // [RH] Clear all ThingID hash chains. - AActor::ClearTIDHashes (); // [RH] clear out the mid-screen message C_MidPrint (NULL, NULL); From 6225f60eb210fcdf3341b7233854a6715b241ac0 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Tue, 31 Jan 2017 11:05:29 +0200 Subject: [PATCH 05/11] Added cheat flag for console variables CVAR with this flag can be set in console or from command line when sv_cheats is enabled There is no such restriction for changing its value from ACS, via SetCVar() and related functions 'cheat' modifier can be used in CVARINFO lump to create variable of this kind --- src/c_cvars.cpp | 8 ++++++++ src/c_cvars.h | 1 + src/d_main.cpp | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/c_cvars.cpp b/src/c_cvars.cpp index fe859cb49c..11ebf55748 100644 --- a/src/c_cvars.cpp +++ b/src/c_cvars.cpp @@ -1617,8 +1617,16 @@ void C_ArchiveCVars (FConfigFile *f, uint32 filter) } } +EXTERN_CVAR(Bool, sv_cheats); + void FBaseCVar::CmdSet (const char *newval) { + if ((GetFlags() & CVAR_CHEAT) && !sv_cheats) + { + Printf("sv_cheats must be true to set this console variable.\n"); + return; + } + UCVarValue val; // Casting away the const is safe in this case. diff --git a/src/c_cvars.h b/src/c_cvars.h index cf6975b866..98dc71915c 100644 --- a/src/c_cvars.h +++ b/src/c_cvars.h @@ -63,6 +63,7 @@ enum CVAR_NOSAVE = 4096, // when used with CVAR_SERVERINFO, do not save var to savegame CVAR_MOD = 8192, // cvar was defined by a mod CVAR_IGNORE = 16384,// do not send cvar across the network/inaccesible from ACS (dummy mod cvar) + CVAR_CHEAT = 32768,// can be set only when sv_cheats is enabled }; union UCVarValue diff --git a/src/d_main.cpp b/src/d_main.cpp index aa90d6c463..e0676b7519 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1398,6 +1398,10 @@ void ParseCVarInfo() { cvarflags &= ~CVAR_ARCHIVE; } + else if (stricmp(sc.String, "cheat") == 0) + { + cvarflags |= CVAR_CHEAT; + } else { sc.ScriptError("Unknown cvar attribute '%s'", sc.String); From 1cdfcb4935f2cdbc28cefb6ef1c716088643f030 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Tue, 31 Jan 2017 15:56:58 +0200 Subject: [PATCH 06/11] RDTSC-based cycle_t for macOS Windows and macOS now share most of related code Old implementation using mach_absolute_time() was more precise (at least in theory) but too costly --- src/posix/cocoa/i_system.mm | 21 +++++++++++ src/stats.cpp | 9 ----- src/stats.h | 75 +------------------------------------ 3 files changed, 22 insertions(+), 83 deletions(-) diff --git a/src/posix/cocoa/i_system.mm b/src/posix/cocoa/i_system.mm index 2e27a80028..35b71c64b8 100644 --- a/src/posix/cocoa/i_system.mm +++ b/src/posix/cocoa/i_system.mm @@ -36,6 +36,7 @@ #include #include #include +#include #include "d_ticcmd.h" #include "doomdef.h" @@ -98,9 +99,29 @@ void SetLanguageIDs() void I_InitTimer(); void I_ShutdownTimer(); +double PerfToSec, PerfToMillisec; + +static void CalculateCPUSpeed() +{ + long long frequency; + size_t size = sizeof frequency; + + if (0 == sysctlbyname("machdep.tsc.frequency", &frequency, &size, nullptr, 0) && 0 != frequency) + { + PerfToSec = 1.0 / frequency; + PerfToMillisec = 1000.0 / frequency; + + if (!batchrun) + { + Printf("CPU speed: %.0f MHz\n", 0.001 / PerfToMillisec); + } + } +} + void I_Init(void) { CheckCPUID(&CPU); + CalculateCPUSpeed(); DumpCPUInfo(&CPU); atterm(I_ShutdownSound); diff --git a/src/stats.cpp b/src/stats.cpp index c57b326401..237436c413 100644 --- a/src/stats.cpp +++ b/src/stats.cpp @@ -42,15 +42,6 @@ #include "m_swap.h" #include "sbar.h" - -#if defined (__APPLE__) - -mach_timebase_info_data_t cycle_t::s_info; -bool cycle_t::s_initialized; - -#endif // __APPLE__ - - FStat *FStat::FirstStat; FStat::FStat (const char *name) diff --git a/src/stats.h b/src/stats.h index d90efa7e40..3166339839 100644 --- a/src/stats.h +++ b/src/stats.h @@ -36,78 +36,7 @@ #include "zstring.h" -#ifndef _WIN32 - -#if defined (__APPLE__) - - -#include - - -class cycle_t -{ -public: - cycle_t() - { - if ( !s_initialized ) - { - mach_timebase_info( &s_info ); - s_initialized = true; - } - - Reset(); - } - - cycle_t &operator=( const cycle_t &other ) - { - if ( &other != this ) - { - m_seconds = other.m_seconds; - - } - - return *this; - } - - void Reset() - { - m_seconds = 0; - } - - void Clock() - { - m_seconds -= Nanoseconds() * 1e-9; - } - - void Unclock() - { - m_seconds += Nanoseconds() * 1e-9; - } - - double Time() - { - return m_seconds; - } - - double TimeMS() - { - return m_seconds * 1e3; - } - -private: - double m_seconds; - - static mach_timebase_info_data_t s_info; - static bool s_initialized; - - uint64_t Nanoseconds() const - { - return mach_absolute_time() * s_info.numer / s_info.denom; - } - -}; - -#else // !__APPLE__ +#if !defined _WIN32 && !defined __APPLE__ #ifdef NO_CLOCK_GETTIME class cycle_t @@ -171,8 +100,6 @@ private: #endif -#endif // __APPLE__ - #else // Windows From b12c8a8f791b9b16d8532b9da613e6759f7c7105 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Tue, 31 Jan 2017 16:00:14 +0200 Subject: [PATCH 07/11] RDTSC-based glcycle_t for macOS Time profiler implementation is now closer to Windows version --- src/gl/utility/gl_clock.cpp | 11 +++++++++++ src/gl/utility/gl_clock.h | 27 ++++++++++----------------- src/posix/cocoa/i_video.mm | 3 +++ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/gl/utility/gl_clock.cpp b/src/gl/utility/gl_clock.cpp index 68a2b06e58..2377029c2d 100644 --- a/src/gl/utility/gl_clock.cpp +++ b/src/gl/utility/gl_clock.cpp @@ -40,6 +40,8 @@ #include #define USE_WINDOWS_DWORD +#elif defined __APPLE__ +#include #endif #include "i_system.h" @@ -114,6 +116,15 @@ void gl_CalculateCPUSpeed () gl_SecondsPerCycle = 1.0 / CyclesPerSecond; gl_MillisecPerCycle = 1000.0 / CyclesPerSecond; } + #elif defined __APPLE__ + long long frequency; + size_t size = sizeof frequency; + + if (0 == sysctlbyname("machdep.tsc.frequency", &frequency, &size, nullptr, 0) && 0 != frequency) + { + gl_SecondsPerCycle = 1.0 / frequency; + gl_MillisecPerCycle = 1000.0 / frequency; + } #endif } diff --git a/src/gl/utility/gl_clock.h b/src/gl/utility/gl_clock.h index 4d5163d86f..2ccda639a8 100644 --- a/src/gl/utility/gl_clock.h +++ b/src/gl/utility/gl_clock.h @@ -7,11 +7,11 @@ extern bool gl_benching; -#ifdef _MSC_VER - extern double gl_SecondsPerCycle; extern double gl_MillisecPerCycle; +#ifdef _MSC_VER + __forceinline long long GetClockCycle () { #if _M_X64 @@ -21,10 +21,14 @@ __forceinline long long GetClockCycle () #endif } -#elif defined(__GNUG__) && defined(__i386__) +#elif defined __APPLE__ && (defined __i386__ || defined __x86_64__) -extern double gl_SecondsPerCycle; -extern double gl_MillisecPerCycle; +inline long long GetClockCycle() +{ + return __builtin_ia32_rdtsc(); +} + +#elif defined(__GNUG__) && defined(__i386__) inline long long GetClockCycle() { @@ -42,21 +46,12 @@ inline long long GetClockCycle() #else -extern double gl_SecondsPerCycle; -extern double gl_MillisecPerCycle; - inline long long GetClockCycle () { return 0; } #endif -#if defined (__APPLE__) - -typedef cycle_t glcycle_t; - -#else // !__APPLE__ - class glcycle_t { public: @@ -100,8 +95,6 @@ private: long long Counter; }; -#endif // __APPLE__ - extern glcycle_t RenderWall,SetupWall,ClipWall; extern glcycle_t RenderFlat,SetupFlat; extern glcycle_t RenderSprite,SetupSprite; @@ -122,4 +115,4 @@ void CheckBench(); void checkBenchActive(); -#endif \ No newline at end of file +#endif diff --git a/src/posix/cocoa/i_video.mm b/src/posix/cocoa/i_video.mm index 2ee7458a8a..637ca0d7fc 100644 --- a/src/posix/cocoa/i_video.mm +++ b/src/posix/cocoa/i_video.mm @@ -534,6 +534,9 @@ CocoaVideo::CocoaVideo() { memset(&m_modeIterator, 0, sizeof m_modeIterator); + extern void gl_CalculateCPUSpeed(); + gl_CalculateCPUSpeed(); + // Create OpenGL pixel format NSOpenGLPixelFormat* pixelFormat = CreatePixelFormat(OpenGLProfile::Core); From 47faaa87fcc7a497cd138041ed6c13a742dd671c Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Tue, 31 Jan 2017 17:41:44 +0200 Subject: [PATCH 08/11] Fixed deprecation warning reported by Clang warning: 'register' storage class specifier is deprecated and incompatible with C++1z [-Wdeprecated-register] --- src/stats.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats.h b/src/stats.h index 3166339839..8ac1e978a7 100644 --- a/src/stats.h +++ b/src/stats.h @@ -130,7 +130,7 @@ inline unsigned long long rdtsc() if (CPU.bRDTSC) #endif { - register unsigned long long tsc; + unsigned long long tsc; asm volatile ("\trdtsc\n" : "=A" (tsc)); return tsc; } From 3d147a032c688342dfa681d9f82e157f9ef42682 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Tue, 31 Jan 2017 17:51:12 +0200 Subject: [PATCH 09/11] Proper RDTSC implementation for x86_64 targets Higher 32 bits of Time Stamp Counter were ignored in non-MSVC Windows and macOS builds --- src/stats.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/stats.h b/src/stats.h index 8ac1e978a7..f18159616d 100644 --- a/src/stats.h +++ b/src/stats.h @@ -102,7 +102,7 @@ private: #else -// Windows +// Windows and macOS #include "x86.h" extern double PerfToSec, PerfToMillisec; @@ -126,15 +126,19 @@ inline unsigned __int64 rdtsc() #else inline unsigned long long rdtsc() { -#ifndef __amd64__ +#ifdef __amd64__ + unsigned long long tsc; + asm volatile ("rdtsc; shlq $32, %%rdx; orq %%rdx, %%rax" : "=a" (tsc) :: "%rdx"); + return tsc; +#else // i386 if (CPU.bRDTSC) -#endif { unsigned long long tsc; asm volatile ("\trdtsc\n" : "=A" (tsc)); return tsc; } return 0; +#endif // __amd64__ } #endif From ccacc2390504a695daa28f49aba3d0c2c9fc639b Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 1 Feb 2017 00:20:08 +0100 Subject: [PATCH 10/11] - fixed division by 0 in ActorMover code. --- wadsrc/static/zscript/shared/movingcamera.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wadsrc/static/zscript/shared/movingcamera.txt b/wadsrc/static/zscript/shared/movingcamera.txt index 0fc50f3e2f..15fdd08368 100644 --- a/wadsrc/static/zscript/shared/movingcamera.txt +++ b/wadsrc/static/zscript/shared/movingcamera.txt @@ -287,7 +287,7 @@ class PathFollower : Actor if (Interpolate ()) { - Time += (8. / (CurrNode.args[1] * TICRATE)); + Time += (8. / (max(1, CurrNode.args[1]) * TICRATE)); if (Time > 1.) { Time -= 1.; From 90ee22b76067d98c14fa6e39af286376aefa5a6f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 1 Feb 2017 01:13:26 +0100 Subject: [PATCH 11/11] - don't let monsters without a See state disappear if they try to enter it. --- src/p_enemy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index e93f41ce53..2926e4e8cf 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -2049,7 +2049,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_Look) } } - if (self->target) + if (self->target && self->SeeState) { self->SetState (self->SeeState); }