{"action":"created","issue":{"url":"https://api.github.com/repos/godotengine/godot/issues/4042","repository_url":"https://api.github.com/repos/godotengine/godot","labels_url":"https://api.github.com/repos/godotengine/godot/issues/4042/labels{/name}","comments_url":"https://api.github.com/repos/godotengine/godot/issues/4042/comments","events_url":"https://api.github.com/repos/godotengine/godot/issues/4042/events","html_url":"https://github.com/godotengine/godot/pull/4042","id":140527945,"number":4042,"title":"Live Script Reloading","user":{"login":"SaracenOne","id":12756047,"avatar_url":"https://avatars.githubusercontent.com/u/12756047?v=3","gravatar_id":"","url":"https://api.github.com/users/SaracenOne","html_url":"https://github.com/SaracenOne","followers_url":"https://api.github.com/users/SaracenOne/followers","following_url":"https://api.github.com/users/SaracenOne/following{/other_user}","gists_url":"https://api.github.com/users/SaracenOne/gists{/gist_id}","starred_url":"https://api.github.com/users/SaracenOne/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SaracenOne/subscriptions","organizations_url":"https://api.github.com/users/SaracenOne/orgs","repos_url":"https://api.github.com/users/SaracenOne/repos","events_url":"https://api.github.com/users/SaracenOne/events{/privacy}","received_events_url":"https://api.github.com/users/SaracenOne/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/godotengine/godot/labels/feature%20proposal","name":"feature proposal","color":"1144ff"},{"url":"https://api.github.com/repos/godotengine/godot/labels/topic:gdscript","name":"topic:gdscript","color":"ffdd44"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2016-03-13T20:46:41Z","updated_at":"2016-03-13T21:00:17Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/godotengine/godot/pulls/4042","html_url":"https://github.com/godotengine/godot/pull/4042","diff_url":"https://github.com/godotengine/godot/pull/4042.diff","patch_url":"https://github.com/godotengine/godot/pull/4042.patch"},"body":"Okay, this is something I'm hoping will be exciting for some people. Scripts can now reload themselves automatically when saved, whether for in-editor tool scripts or ingame via the live edit protocol.\r\n\r\nI should explain a little bit about how it works internally why I made certains decision on this experimental implementation. Firstly, regarding tool scripts, the way the editor currently handles non-tool scripts is that when they are instanced, if the script server is turned off which it is when in editor mode, the object using a particular script is simply given a null instance of that script, so that even if it is registered to run as a process prior, it won't do anything. Since in my implementation I want to allow non-tool scripts to change into tool scripts a vice-versa, script still need to keep an internal list of objects its attached to. In this case, I used the seemingly unused 'placeholder' list, giving objects a 'placeholder' reference when they attempt to instance a non-tool script with the scripting-engine disabled. When reloaded, instances and placeholders can convert between each other and vice-versa. The pre-existing 'placeholders' might not be the ideal way of storing this, since this implementation currently does not attempt to save members between placeholders and instances, but this can easily be changed depending on how this system should develop going forward. When reloading between instanced scripts, we simply copy the members array, resize the original if nessecary, and then attempt to copy the variables from the old one to the new one using simply string comparisons.\r\n\r\nNext point is how script attempt to update themselves, particular in relation to script which inherit from other scripts. Internally, this implementation using a 'dirty script list' which attempts to keep a list of all saved scripts ordered based on inheritance hierarchy, meaning that if a script of which another script inherits from is modified, the inheritor will be added to list only after the initial script has updated. Internally, this is done by scanning the 'inheritors' list in a script object which is tool-specific internal list of objects inheriting this script, not the scripts themselves. A reference-counted list of inherited scripts would be slightly more efficent here, but I didn't really want to change more than I felt I have to in this early implementation, but this can always be refined further. Ensuring the correct order of script reloads in relation to inheritance should work fairly reliably, but it may need to be tested more extensively just in case there's some rare edge-cases I haven't anticipated. This part of the code is mostly confined to the script editor, and there are also some fixes in regards to the various methods of saving scripts, as well as handling scripts being modified from outside of the editor. It should be worth noting that there isn't a really good interface for 'reloading' preexisting resources, so there's some vaguely hacky workarounds going on which have been noted in comments. There's also a fix in another pull request I just sent concerning fixes to the text editor regarding undo/redo, but I decided to send them seperately since while they do affect this feature, it's still kind of a self-contained fix.\r\n\r\nFinally, there's reloading scripts in the game itself. Internally, it uses the same ordered dirty list on the editor side as on the game side. It creates a 'live edit' packet consisting of two arrays, one containing the names of the scripts requiring reload, and the other containing the modified source code for each file. The rest is fairly self-explanatory. It might be slightly inefficient to send the entire source code for each file over the protocol, but it shouldn't be a problem for now considering it only does it for files detected as modified, and at most would only ever be done over an internal wi-fi connection. The reason for doing it like this is of course the afformentioned lack of a good interface for reloading resources, but this method should also ensure that script reloading works when debugging a game on an alternative device like a tablet.\r\n\r\nOne final thing, this feature currently depends on a setting in the text editor 'dynamic_script_reloading_enabled' which is set to be off by default since this is still a somewhat experimental feature, and of course, 'live edit' has to enabled to use this feature with ingame scripts.\r\n\r\nThat should cover everything, but please ask if there's anything about this implementation which is unclear. This came out of some experiments to see if the editor could be made more 'WYSIWYG-like', so hopefully this should help with speeding up iteration time in game development."},"comment":{"url":"https://api.github.com/repos/godotengine/godot/issues/comments/196052596","html_url":"https://github.com/godotengine/godot/pull/4042#issuecomment-196052596","issue_url":"https://api.github.com/repos/godotengine/godot/issues/4042","id":196052596,"user":{"login":"SaracenOne","id":12756047,"avatar_url":"https://avatars.githubusercontent.com/u/12756047?v=3","gravatar_id":"","url":"https://api.github.com/users/SaracenOne","html_url":"https://github.com/SaracenOne","followers_url":"https://api.github.com/users/SaracenOne/followers","following_url":"https://api.github.com/users/SaracenOne/following{/other_user}","gists_url":"https://api.github.com/users/SaracenOne/gists{/gist_id}","starred_url":"https://api.github.com/users/SaracenOne/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SaracenOne/subscriptions","organizations_url":"https://api.github.com/users/SaracenOne/orgs","repos_url":"https://api.github.com/users/SaracenOne/repos","events_url":"https://api.github.com/users/SaracenOne/events{/privacy}","received_events_url":"https://api.github.com/users/SaracenOne/received_events","type":"User","site_admin":false},"created_at":"2016-03-13T21:00:17Z","updated_at":"2016-03-13T21:00:17Z","body":"Fixing it now. For some reason, this syntax issue didn't show up in my compiler."}}