-
Notifications
You must be signed in to change notification settings - Fork 588
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
Prototype for moving core functionality into a Swift framework #3573
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pull request does not contain a valid label. Please add one of the following labels: ['pr-fix', 'pr-change', 'pr-feature', 'pr-maintenance']
I love this!!! Will have a proper look/play soon! |
This now covers three extensions - hs.math, hs.base64 and hs.camera. hs.math is trivial, hs.base64 is also relatively simple, but showed a nice reduction in complexity (which, to be fair, we could have achieved using NSData without involving Swift), and hs.camera is a properly representative, complex extension with watchers. hs.camera is not yet fully working - the isInUse KVO watcher doesn't seem to be working, but I'm not sure why yet. Possibly because the app needs Camera access permissions. Also I strongly dislike that one LuaSkin thing (callback lua ref) has now leaked into Hammertime, and one thing (camera object canary) is missing. Both of these call for switching the C-side userdata object to a struct that contains the canary, callback ref and camera object, rather than having the camera object be the userdata. This is something @asmagill and I have discussed previously and I've forgotten which of us preferred using structs and which preferred directly using ObjC objects as userdata, but whichever way round we were, this prototype indicates that structs are the only sane option if we want Hammertime to remain completely isolated from Lua (which we should want, because otherwise there's little point abstracting all the core functionality of the extensions into a framework). Would welcome thoughts and reviews. I'll push to this a little more tomorrow to take care of the FIXMEs. |
I am on the pro Object side because it simplifies allowing the use of one module's objects in another module without actually having to have any specific code to know how to handle the object -- it's always a (possibly distant) descendant of NSObject and can always be treated that way. A structure could probably provide a similar "simplicity of mixing" if we insisted that the first three members in the structure were always the same and in the same order (the object, the callback, and the canary, for example) with any additional unique pieces of information afterwards -- then the structure could be cast to the base type to get at the core object if required in another module. |
@asmagill we could also make it an ObjC class, with an Edit: I'll rework this PR to do exactly that - having an HSuserdata class probably actually makes tons of sense. |
…r all definable range methods
Ok, so a hosting ObjC class turned out to not be great because then those have to be cached on the ObjC side as well as the Swift side having to cache its objects. Instead I added a raw pointer on the Swift side for the ObjC side to do what it likes with - in this case, storing a struct there which has the callbackRef and canary. Oddly, the That aside, I am now pretty happy with the state of hs.camera. |
If anyone wants to play with the part that isn't working:
cam = hs.camera.allCameras()[1]
print(cam:name())
cam:setPropertyWatcherCallback(function() print("isInUse changed") end)
cam:startPropertyWatcher() If you now fire up something that uses the webcam, you'll see Now run Finally, either close or re-open the webcam-using app. You'll see the |
… because it seems to behave properly when removing the watcher
@latenitefilms @asmagill one for y'all to chew on :)
I've been thinking more about the possible roads to Hammerspoon 2.0 and it seems to me like there isn't enough energy or Swift experience in the project to tackle a ground-up rewrite.
So, it therefore seems to me to make sense to try and explore ways to modernise the project in isolated chunks.
We've previously figured out how to write an extension in pure Swift, but we never tackled what I think is probably the most important piece - extracting and isolating the core functionality of extensions, into a Framework. I've said before that I strongly dislike how intertwined all of the "Do Useful Thing" code is with the "Bridge with Lua" code.
This PR serves a a very simple example of how one can add a pure Swift framework that does useful things, while leaving the Lua bridging parts in ObjC.
Of particular note is an opportunity for bike-shedding around error handling - if you compare the changes to
math_randomFloatFromRange
with those tomath_randomFromRange
, in the former I am throwing an exception from Swift when the range values are impossible, and merely catching that exception in ObjC. In the latter I am validating the range from ObjC and assuming the Swift will always succeed.Personally I prefer the former, mostly because keeps the ObjC code to just "get values out of Lua, call useful method, return values to Lua".
If we are ok with this sort of thing, we would be able to convert modules to this form one at a time (and take the opportunity to modernise their APIs, with appropriate backwards compatibility where at all possible) and then later rewrite the Lua bridging part to also be Swift. (I deliberately chose not to replace the Lua bridging parts with pure Swift in this example, because I suspect there will be cases where we want to partially migrate something)
Then separately we can consider when to rewrite the core binary in Swift, and then once all of those things have happened, we would have a mostly-Swift codebase, but one that still relied a lot on NSObject. We'd then be in a position to rewrite LuaSkin and converge on native Swift types, and by that stage everything will be neatly isolated/encapsulated such that it's relatively easy to also do that in chunks that are likely to succeed individually.
I don't know if any of those things will actually happen, but this seems to me like a plan which is more likely to succeed because it doesn't fail if we only ever do part of it.