Guest User

verse_crash_detector (VERSE CODE)

a guest
May 23rd, 2024
359
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.61 KB | None | 1 0
  1. ####################################################
  2. # Copyright 2024 LAMA (https://x.com/lama_creator)
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  4. # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  5. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6. ####################################################
  7.  
  8. # VERSION: 24_02b
  9.  
  10. ####################################################
  11. ##################### README #######################
  12. ####################################################
  13. # DISCLAIMERS:
  14. # - Those are only things I discovered by myself by doing some little testing, so I might be wrong about some things
  15. # - Use at your own risk, as every Verse code, this code could stop working if Epic makes changes on fortnite devices that rely on it or on the crash behaviour itself
  16. #
  17. # IMPORTANT:
  18. # The printed/reported crashed device isn't necessarily the device that contains the faulty code (read more below)
  19. #
  20. # HOW IT WORKS:
  21. # When a crash occurs, the whole stack trace that caused the crash is aborted
  22. # (thus the top most caller device of the stack trace will be the one to crash)
  23. #
  24. # Since the top most calling function of all devices is (almost?) always an OnBegin function,
  25. # the crashed device will crash everything that's been called inside its own OnBegin (even spawned coroutines)
  26. # but the crashed device functions can still be called
  27. #
  28. # NOTES:
  29. # - Has been tested on 100M+ players maps
  30. # - Doesn't detect server crashes (Epic ones)
  31. # - Doesn't detect infinite loops
  32. # - The per device CrashAnalytics field is not required (will send a global crash report instead)
  33. # - A 'non crashable' device can trigger a crash on a crashable device (if it's the crashable device that called the faulty code)
  34. ####################################################
  35.  
  36. ####################################################
  37. ###################### TODO ########################
  38. ####################################################
  39. # Add an in game UI for whenever a crash is detected (so that players can also report it and add context to it)
  40. # Allow for more granular crash detection
  41. # Find a way to find the device that contains the faulty code?
  42. # Find a way to restart the crashed device?
  43. # Allow script to dynamically register/unregister devices that gets loaded/unloaded from data layers
  44.  
  45. using { /Fortnite.com/Devices }
  46. using { /Verse.org/Simulation }
  47. using { /Verse.org/Random }
  48. using { /Verse.org/Colors }
  49.  
  50. # Allow for printing an error without crashing the whole device
  51. PrintError(String: string):void = Print("Error: {String}", ?Color := NamedColors.Red)
  52. PrintOutputLog(String: string):void = Print(String, ?Duration := 0.0)
  53.  
  54. var RegisteredCrashableDevices : weak_map(session, [crashable_device]crashable_device) = map{}
  55. RegisterCrashableDevice(Device: crashable_device):void=
  56. Session := GetSession()
  57. if(not RegisteredCrashableDevices[Session]):
  58. Print("Registering crashable devices, see Output Log for the detailed list")
  59. option{set RegisteredCrashableDevices[Session] = map{}}
  60.  
  61. if(not RegisteredCrashableDevices[Session][Device]):
  62. PrintOutputLog("Successfully registered crashable device '{Device}' (CAN_RESTART_ROUND={Device.RestartRoundOnCrash? and "YES" or "NO"})")
  63. option{set RegisteredCrashableDevices[Session][Device] = Device}
  64. else:
  65. PrintError("Tried to register crashable device '{Device}' twice")
  66.  
  67. crashable_device := class<unique>(creative_device):
  68.  
  69. @editable
  70. DeviceName : string = ""
  71.  
  72. @editable
  73. var<private> RestartRoundOnCrash : logic = true
  74.  
  75. # Naming convention for the "Event Name" is crash_{device_name}
  76. @editable
  77. CrashAnalytics : ?analytics_device = false
  78.  
  79. var<private> IsAlive : logic = true
  80.  
  81. PingEvent<private> : event() = event(){}
  82. CrashEvent<private> : event() = event(){}
  83.  
  84. # Any (3568) error here means you need to rename all your (crashable_device).OnBegin() functions by OnCrashableDeviceBegin()
  85. OnBegin<override><final>()<suspends>:void=
  86. # This suspending thread has to be spawned by Self from Self in order for it to crash when something else crashes inside the device
  87. spawn{WatchPingEvents()}
  88. spawn{AwaitManualCrashTest()}
  89.  
  90. # Register before overridden OnBegin so we don't have to deal with OnBegin methods that never finish
  91. RegisterCrashableDevice(Self)
  92.  
  93. OnCrashableDeviceBegin()
  94.  
  95. OnCrashableDeviceBegin<public>()<suspends>:void={}
  96.  
  97. # This thread will crash if Verse crashes
  98. WatchPingEvents<private>()<suspends>:void=
  99. loop:
  100. PingEvent.Await()
  101. set IsAlive = true
  102.  
  103. # needs no_rollback :'(
  104. Ping<public>():logic=
  105. set IsAlive = false
  106. PingEvent.Signal()
  107. IsAlive
  108.  
  109. Crash(?AllowRoundToRestart : logic = true):void=
  110. if(not AllowRoundToRestart?):
  111. set RestartRoundOnCrash = false
  112. CrashEvent.Signal()
  113.  
  114. AwaitManualCrashTest<private>()<suspends>:void=
  115. CrashEvent.Await()
  116. Sleep(0.0) # If we don't do so, the detected crashed device will be the caller of the Crash() method
  117. Err("!!! CrashTest !!!")
  118.  
  119. # OVERRIDE ME: If you don't want to use the DeviceName field because you already have a way to dynamically generate a name
  120. GetDeviceName()<transacts><decides>:string = (__:?string=false)?
  121.  
  122. ToString(CrashableDevice: crashable_device)<transacts>:string =
  123. FoundName := CrashableDevice.GetDeviceName[] or CrashableDevice.DeviceName
  124. FoundName.Length > 0 and FoundName or "UNKNOWN"
  125.  
  126. verse_crash_detector := class(creative_device):
  127.  
  128. @editable
  129. Timer : timer_device = timer_device{}
  130.  
  131. @editable
  132. CanRestartRoundOnCrash : logic = true
  133.  
  134. @editable
  135. GlobalCrashAnalytics : analytics_device = analytics_device{}
  136.  
  137. @editable
  138. CRASH_BUTTON : ?button_device = false
  139.  
  140. OnBegin<override>()<suspends>:void=
  141. Timer.Disable() # Set default behaviour to "safe mode" (do not end round)
  142.  
  143. # Listen to registered devices crashes
  144. spawn{WatchCrashes()}
  145.  
  146. if(CrashButton := CRASH_BUTTON?):
  147. loop:
  148. CrashButton.InteractedWithEvent.Await()
  149. if(RandomDevice := Shuffle(GetAliveDevices())[0]):
  150. RandomDevice.Crash() # Triggers a simulated internal Verse crash
  151.  
  152. GetAliveDevices()<transacts>:[]crashable_device =
  153. for(Device : RegisteredCrashableDevices[GetSession()], Device.IsAlive?) { Device }
  154.  
  155. WatchCrashes<private>()<suspends>:void=
  156. # Allow for round to restart when a crash occurs
  157. Timer.Enable()
  158. Timer.Start()
  159.  
  160. # Constantly look for any crashed device
  161. loop:
  162. for(Device : GetAliveDevices()):
  163. Pong := Device.Ping()
  164.  
  165. # Crash detected
  166. if(not Pong?):
  167.  
  168. # Send analytics data if analytics is plugged
  169. if(Analytics := Device.CrashAnalytics?, AnyAgent := GetPlayspace().GetPlayers()[0]):
  170. Analytics.Submit(AnyAgent)
  171.  
  172. PrintError("verse_crash_detector: Device '{Device}' called a function that caused a crash")
  173.  
  174. # Only restart round if it's needed
  175. if(CanRestartRoundOnCrash?, Device.RestartRoundOnCrash?):
  176. # Let the timer end -> trigger HUD -> send global crash analytics -> restart round
  177. break
  178. else:
  179. # Send global crash analytics if device crash analytics is not set
  180. if(not Device.CrashAnalytics?, AnyAgent := GetPlayspace().GetPlayers()[0]):
  181. GlobalCrashAnalytics.Submit(AnyAgent)
  182.  
  183. Timer.Reset()
  184. Timer.Start()
  185. Sleep(2.0)
Advertisement
Add Comment
Please, Sign In to add comment