How to Support Multipeer Connectivity ?
Pythonista can support iOS Multipeer Connectivity?
who can show some sample code?
You could try to figure out which object is crashing, by manually del'ing them, one at a time. (my guess - services are getting del'd before delegates). Then, add an extra retain() for the offending objects.
Alternatively, maybe try setDelegate_(None) on everything that takes a delegate, to disentangle things.
Trying to run incrementally more code after the crash to find the culprit failed, as Pythonista will crash if I execute just an empty file.
Setting delegates to None has not changed things.
deleting either the session object or the advertiser object both cause a crash after a successful connect, but do not cause a crash if I do the deletes after running the server code without a connection.
@JonB, as this is the code that gets run on an invite, which seems to be the crucial point causing the crashes, and as I understand next to nothing about it, I have to ask if there is anything you see that messes things up?
class _block_descriptor (Structure): _fields_ = [('reserved', c_ulong), ('size', c_ulong), ('copy_helper', c_void_p), ('dispose_helper', c_void_p), ('signature', c_char_p)] InvokeFuncType = ctypes.CFUNCTYPE(None, *[c_void_p,ctypes.c_bool,c_void_p]) class block_literal(Structure): _fields_ = [('isa', c_void_p), ('flags', c_int), ('reserved', c_int), ('invoke', InvokeFuncType), ('descriptor', _block_descriptor)] # Advertiser Delegate def advertiser_didReceiveInvitationFromPeer_withContext_invitationHandler_(_self,_cmd,advertiser,peerID,context,invitationHandler): print('invitation',peerID) blk=block_literal.from_address(invitationHandler) blk.invoke(ObjCInstance(invitationHandler),True, ObjCInstance(mySession))
can you print (console)
.retainCount()for the advertiser and session objects, for the case when no invite happens, then after the invite occurs?
What I am wondering is if something is getting released by the invitationHandler -- that bit of code is taking the block that is passed to the delegate method, turning it into an actual ObjCBlock, then invoking it.
One thing you could do is override the
__del__method of the suspect ObjCInstances -- maybe set up logging to a file in your script, then insert a logger.debug(self), that way we can see which object is the culprit.
Next, you could try first calling
retain()on those instances -- that may cause a memory leak, but probably not a crash.
Before a connection, both advertiser and session report a retainCount of 2.
After a connection has been established and closed, calling retainCount on either object crashes Pythonista.
Printing counts within the callback, before the invitation invoke gives these:
- Advertiser: 5
- Session: 2
And immediately after:
- Advertiser: 6
- Session: 4
Have you tried calling retain, then del the object?
Alternatively -- just monkey patch
__del__so it is a pass only.
Tried mySession.retain() on the console after a connection —> crash.
Tried retain() on both session and advertiser immediately after creation, then del’ing them on close. Next run still crashes.
__del__of both objects to a dummy. Next run still crashes in
objc_util.__del__, likewise if I try a
del mySessionon console, so I am probably not monkeypatching properly.
had the same problem. The crash was caused by the garbage collection which is triggered automatically somewhen when python wants to delete the invitationHandler.
My approach/workaround was not to get it deleted by remembering its reference. That way the garbage collector will not call delete on it.
inviHandler = ObjCInstance(invitationHandler)
my guess was that something in the MPC framework still holds the reference and uses this invitation handler but the pythonista garbage collector does not know this because it only knows about references in your python programm. Thus when the python gc deletes it, and after that something in the apple mpc framework wants to call it, it will inevitably crash.
instead of someglobalscopedobject you could also use something like that to make it able to be reentrant
inviHandlerObj = ObjCInstance(invitationHandler)
if (builtins.retainCache == NONE):
builtins.retainCache = [ ]
I used the objc_util.retain_global, and updated the repository with the new version. Next I will package this into a Pythonic API that does not have client-server roles but only peers.
# Advertiser Delegate def advertiser_didReceiveInvitationFromPeer_withContext_invitationHandler_(_self,_cmd,advertiser,peerID,context,invitationHandler): global mySession invitation_handler = ObjCInstance(invitationHandler) retain_global(invitation_handler) blk=block_literal.from_address(invitationHandler) blk.invoke(invitation_handler,True, mySession)
Yes that looks promising. I just uploaded my work on this for you at github. It doesn't crash and is completely reentrant. The idea was to make an simple API which games and other apps can import. But I did not work on it recently. When I saw your post that you too want to build an API I thought you might want to have a look. https://github.com/mithrendal/pythonista_mpc
Btw I am a big fan of your Gestures.py API 😀
How do I send data from client to server and vise versa during game?
@robertiii be aware, my code on github is still unfinished. I did this some months ago to proof that MPC is really working in Pythonista . Thats what the code does well. I uploaded it then for @mikael as an working example. But it does not yet serve as an solid and simple API, a lot of polishing and refactoring is still needed here. I am still at it, but my time for it is restricted so don't expect a finished API very soon. Of course you decide, but I would wait for @mikael 's Grand Python Simplyfication !!! That is what I am also looking forward to ;-) because he has already done some great APIs for pythonista (see here https://github.com/mikaelho).
Here's a minimal usage example, a line-based chat:
import multipeer my_name = input('Name: ') mc = multipeer.MultipeerConnectivity( display_name=my_name, service_type='chat' ) try: while True: chat_message = input('Message: ') mc.send(chat_message) finally: mc.end_all()
This is a functional chat, even though the prompts and incoming messages tend to get messily mixed up.
You can also run the
multipeer.pyfile to try out a cleaner Pythonista UI version of the chat, which demonstrates sending dicts as the message content, and acts as the best 'how-to guide' at the moment.
Docs still need some significant work. All testing and comments highly welcome.
Noted that shutting and restarting the UI chat client leads to duplicate messages being received, at least with 3 peers, and general instability. I will try including the checks to not re-create classes when already available.
Also, need to implement proper error handling and exceptions if possible.
btw, those try/except to not recreate the classes would not have worked,
would fail because SessionDelegate was cleared.
You might first try
though, note that the objc class name can change when using "debug" mode.
@JonB, thanks. I would like to say that I understood completely what you said... But no, I didn’t. For clarity, I understood that the thing below would be sufficient, barring debug on/off people?
try: AdvertiserDelegate = ObjCClass('AdvertiserDelegate') ADelegate = AdvertiserDelegate.alloc().init() except: # ...
Eh, the issue was with the UI chat demo code, not the MC framework. Seems to work pretty well now, even tolerates and recovers from being backgrounded without any additional effort from the developer.
Next I will take a look at capturing and managing errors from send, mainly.
Would be good to hear if someone has a use case for the streaming or resource-send options, which are much more tricky.
create_objc_class will create a new class name every time it is run (at least, with the default debug attrib set to 1, ) -- so
'AdvertiserDelegate'might really become
Only really an issue for development, but if you see strange things where a change you just made doesn't seem to be working, thats the reason.
@mikael very super 👍🏿 cool indeed. I do really love your approach with this simple API. Just tried it and it works excellent. Will have a look at the source later...
Edit: made a minor bugfix pull request to avoid an exception of being thrown