How to Support Multipeer Connectivity ?
Pythonista can support iOS Multipeer Connectivity?
who can show some sample code?
I am trying to get this to work, but the following just crashes Pythonista (latest beta). All fine if you uncomment the last line. Any ideas?
#coding: utf-8 from objc_util import * NSBundle.bundle(Path="/System/Library/Frameworks/MultipeerConnectivity.framework").load() MCPeerID = ObjCClass('MCPeerID') MCNearbyServiceAdvertiser = ObjCClass('MCNearbyServiceAdvertiser') myID = MCPeerID.alloc().initWithDisplayName('peer') advertiser = MCNearbyServiceAdvertiser.alloc().initWithPeer_discoveryInfo_serviceType_(myID, None, 'dev_srv')
Ah, enabled the fault handler and noticed that it was just the service type with an underscore... Nothing to see here, please move along.
They work fine on first run, but the advertiser/server always crashes on the second run.
It only crashes if there was a successful connection on the first run, otherwise it runs fine.
Fault handler provides only a semi-useful error:
Fatal Python error: Segmentation fault Current thread 0x000000016fffb000 (most recent call first): File "/var/containers/Bundle/Application/01E95FCB-DD81-45FB-B878-47D3C0FF9E35/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 682 in __del__
... which points to the last line in this objc_util code:
def __del__(self): # Release the ObjC object's memory: objc_msgSend = c['objc_msgSend'] objc_msgSend.argtypes = [c_void_p, c_void_p] objc_msgSend.restype = None objc_msgSend(self.ptr, sel('release'))
Changes I have tried to the code in this thread, with no effect:
- Added session
disconnect()to the end
- Added try/except to only create the delegate classes when they do not already exist
Any help debugging this would be much appreciated.
- Added session
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: # ...