1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 
25 #include "pxr/pxr.h"
26 #include "pxr/base/tf/type.h"
27 #include "pxr/base/tf/notice.h"
28 #include "pxr/base/tf/pyFunction.h"
29 #include "pxr/base/tf/pyIdentity.h"
30 #include "pxr/base/tf/pyNoticeWrapper.h"
31 #include "pxr/base/tf/pyPtrHelpers.h"
32 #include "pxr/base/tf/pyWeakObject.h"
33 #include "pxr/base/tf/stringUtils.h"
34 #include "pxr/base/tf/type.h"
35 
36 #include <boost/noncopyable.hpp>
37 #include <boost/python/class.hpp>
38 #include <boost/python/def.hpp>
39 #include <boost/python/handle.hpp>
40 #include <boost/python/manage_new_object.hpp>
41 #include <boost/python/return_value_policy.hpp>
42 #include <boost/python/scope.hpp>
43 
44 #include <functional>
45 
46 using std::string;
47 
48 using namespace boost::python;
49 
50 PXR_NAMESPACE_OPEN_SCOPE
51 
52 class Tf_PyNotice {
53 public:
SendWithType(const TfNotice & notice,const TfType & noticeType,const TfWeakBase * sender,const void * senderUniqueId,const std::type_info & senderType)54     static size_t SendWithType(const TfNotice& notice,
55                                const TfType & noticeType,
56                                const TfWeakBase* sender,
57                                const void *senderUniqueId,
58                                const std::type_info & senderType)
59     {
60         return notice._SendWithType(noticeType,
61                                     sender, senderUniqueId, senderType);
62     }
63 };
64 
65 PXR_NAMESPACE_CLOSE_SCOPE
66 
67 PXR_NAMESPACE_USING_DIRECTIVE
68 
69 namespace {
70 
71 // TfNotice is passed for both the type and the base to indicate the root of the
72 // hierarchy.
73 TF_INSTANTIATE_NOTICE_WRAPPER(TfNotice, TfNotice);
74 
75 class Tf_PyNoticeInternal
76 {
77   public:
78 
79     struct Listener : public TfWeakBase, public boost::noncopyable {
80 
81         typedef void CallbackSig(object const &, handle<> const &);
82         typedef std::function<CallbackSig> Callback;
83 
New__anonacb6c7c40111::Tf_PyNoticeInternal::Listener84         static Listener *New(TfType const &noticeType,
85                              Callback const &callback,
86                              TfAnyWeakPtr const &sender)
87         {
88             if (noticeType.IsA<TfNotice>())
89                 return new Listener(noticeType, callback, sender);
90 
91             // Fail -- unknown notice type.
92             TfPyThrowTypeError("not registering for '" +
93                                noticeType.GetTypeName() +
94                                "' because it is not a known TfNotice type");
95             // CODE_COVERAGE_OFF The above throws, so this is unreachable.
96             return 0;
97             // CODE_COVERAGE_ON
98         }
99 
~Listener__anonacb6c7c40111::Tf_PyNoticeInternal::Listener100         ~Listener() { Revoke(); }
Revoke__anonacb6c7c40111::Tf_PyNoticeInternal::Listener101         void Revoke() { TfNotice::Revoke(_key); }
102 
103       private:
104 
Listener__anonacb6c7c40111::Tf_PyNoticeInternal::Listener105         Listener(TfType const &noticeType,
106                  Callback const &callback,
107                  TfAnyWeakPtr const &sender) :
108             _callback(callback), _noticeType(noticeType) {
109 
110             _key = TfNotice::Register
111                 (TfCreateWeakPtr(this), &Listener::_HandleNotice,
112                  noticeType, sender);
113         }
114 
_GetDeliverableNotice__anonacb6c7c40111::Tf_PyNoticeInternal::Listener115         object _GetDeliverableNotice(TfNotice const &notice,
116                                      TfType const &noticeType) {
117             // If the notice type is not wrapped, return the type name in a
118             // string.
119             TfPyLock lock;
120             /// XXX noticeType is incorrect when the notice is
121             /// python-implemented.  We should fix this when TfType optimization
122             /// work is done.
123             object noticeClass = TfPyGetClassObject(typeid(notice));
124             if (TfPyIsNone(noticeClass))
125                 return object(TfType::Find(notice).GetTypeName());
126 
127             // If it's a python notice, use the embedded python object.
128             if (TfPyNoticeWrapperBase const *pyNotice =
129                 TfSafeDynamic_cast<TfPyNoticeWrapperBase const *>(&notice))
130                 return object(pyNotice->GetNoticePythonObject());
131 
132             // Otherwise convert the notice to python like normal.  We
133             // can't just use object(notice) because that won't produce
134             // a notice of the correct derived type.
135             return Tf_PyNoticeObjectGenerator::Invoke(notice);
136         }
137 
_HandleNotice__anonacb6c7c40111::Tf_PyNoticeInternal::Listener138         void _HandleNotice(TfNotice const &notice,
139                            TfType const &type,
140                            TfWeakBase *sender,
141                            void const *senderUniqueId,
142                            const std::type_info &) {
143             TfPyLock lock;
144             object pyNotice = _GetDeliverableNotice(notice, type);
145             if (!TfPyIsNone(pyNotice)) {
146                 // Get the python sender.
147                 handle<> pySender = sender ?
148                     handle<>(allow_null(Tf_PyIdentityHelper::
149                                         Get(senderUniqueId))) : handle<>();
150                 _callback(pyNotice, pySender);
151             }
152         }
153 
154         Callback _callback;
155         TfNotice::Key _key;
156         TfType _noticeType;
157     };
158 
159     static Listener *
RegisterWithAnyWeakPtrSender(TfType const & noticeType,Listener::Callback const & cb,TfAnyWeakPtr const & sender)160     RegisterWithAnyWeakPtrSender(TfType const &noticeType,
161                                  Listener::Callback const &cb,
162                                  TfAnyWeakPtr const &sender)
163     {
164         return Listener::New(noticeType, cb, sender);
165     }
166 
167     static Listener *
RegisterWithPythonSender(TfType const & noticeType,Listener::Callback const & cb,object const & sender)168     RegisterWithPythonSender(TfType const &noticeType,
169                              Listener::Callback const &cb,
170                              object const &sender) {
171         Tf_PyWeakObjectPtr weakSender = Tf_PyWeakObject::GetOrCreate(sender);
172         if (!weakSender)
173             TfPyThrowTypeError("Cannot register to listen to notices from the "
174                                "provided sender.  The sender must support "
175                                "python weak references.");
176 
177         TfAnyWeakPtr holder(weakSender);
178         return RegisterWithAnyWeakPtrSender(noticeType, cb, holder);
179     }
180 
181     static Listener *
RegisterGlobally(TfType const & noticeType,Listener::Callback const & cb)182     RegisterGlobally(TfType const &noticeType, Listener::Callback const &cb) {
183         return RegisterWithAnyWeakPtrSender
184             (noticeType, cb, TfAnyWeakPtr());
185     }
186 
187     static size_t
SendWithAnyWeakPtrSender(TfNotice const & self,TfAnyWeakPtr const & sender)188     SendWithAnyWeakPtrSender(TfNotice const &self,
189                              TfAnyWeakPtr const &sender) {
190         return Tf_PyNotice::SendWithType(self, TfType::Find(&self),
191                                          sender.GetWeakBase(),
192                                          sender.GetUniqueIdentifier(),
193                                          sender.GetTypeInfo());
194     }
195 
196     static size_t
SendWithPythonSender(TfNotice const & self,object const & sender)197     SendWithPythonSender(TfNotice const &self, object const &sender) {
198         // Get a "WeakObjectPtr" corresponding to the sender -- this is a
199         // TfWeakPtr to an object which holds a python weak reference.  This
200         // object expires when the python object expires.  This is what lets us
201         // use arbitrary python objects as senders in the notice system.
202         Tf_PyWeakObjectPtr weakSender = Tf_PyWeakObject::GetOrCreate(sender);
203         if (!weakSender)
204             TfPyThrowTypeError("Cannot send notice from the provided sender.  "
205                                "Sender must support python weak references.");
206         TfAnyWeakPtr holder(weakSender);
207         return SendWithAnyWeakPtrSender(self, holder);
208     }
209 
SendGlobally(TfNotice const & self)210     static size_t SendGlobally(TfNotice const &self) {
211         return Tf_PyNotice::SendWithType(self, TfType::Find(&self),
212                                          0, 0, typeid(void));
213     }
214 
215 };
216 
217 } // anonymous namespace
218 
wrapNotice()219 void wrapNotice()
220 {
221     // Make sure we can pass callbacks from python.
222     TfPyFunctionFromPython<Tf_PyNoticeInternal::Listener::CallbackSig>();
223 
224     // Passing TfNotice for both T and its base indicates that this is the root
225     // of the notice hierarchy.
226     scope notice = TfPyNoticeWrapper<TfNotice, TfNotice>::Wrap("Notice")
227         .def(init<>())
228 
229         // We register the method that takes any python object first, as this is
230         // the last overload that will be tried.  Thus, it will only be invoked
231         // if the python object is not already weak-pointable.
232         .def("Register",
233              Tf_PyNoticeInternal::RegisterWithPythonSender,
234              return_value_policy<manage_new_object>(),
235          "Register( noticeType, callback, sender ) -> Listener \n\n"
236          "noticeType : Tf.Notice\n"
237          "callback : function\n"
238          "sender : object\n\n"
239          "Register a listener as being interested in a TfNotice  "
240          "type from a specific sender.  Notice listener will get sender  "
241          "as an argument.   "
242          "  "
243          "Registration of interest in a notice class N automatically  "
244          "registers interest in all classes derived from N.  When a  "
245          "notice of appropriate type is received, the listening object's "
246          " member-function method is called with the notice. "
247          "  "
248          "  "
249          "To reverse the registration, call Revoke() on the Listener "
250          "object returned by this call. "
251              )
252         .def("Register",
253              Tf_PyNoticeInternal::RegisterWithAnyWeakPtrSender,
254              return_value_policy<manage_new_object>())
255         .staticmethod("Register")
256 
257         .def("RegisterGlobally",
258              Tf_PyNoticeInternal::RegisterGlobally,
259              return_value_policy<manage_new_object>(),
260              "RegisterGlobally( noticeType, callback ) -> Listener \n\n"
261              "noticeType : Tf.Notice\n"
262              "callback : function\n\n"
263              "Register a listener as being interested in a TfNotice "
264              "type from any sender.  The notice listener does not get sender "
265              "as an argument. "
266              "")
267         .staticmethod("RegisterGlobally")
268 
269 
270         // We register the method that takes any python object first, as this is
271         // the last overload that will be tried.  Thus, it will only be invoked
272         // if the python object is not already weak-pointable.
273         .def("Send", &Tf_PyNoticeInternal::SendWithPythonSender,
274              "Send(sender) \n\n"
275              "sender : object \n\n"
276              "Deliver the notice to interested listeners, returning the number "
277              "of interested listeners. "
278              "This is the recommended form of Send.  It takes the sender as an "
279              "argument. "
280              "Listeners that registered for the given sender AND listeners "
281              "that registered globally will get the notice. ")
282         .def("Send", &Tf_PyNoticeInternal::SendWithAnyWeakPtrSender)
283         .def("SendGlobally", &Tf_PyNoticeInternal::SendGlobally,
284              "SendGlobally() \n\n"
285              "Deliver the notice to interested listeners.   "
286              "For most clients it is recommended to use the Send(sender) "
287              "version of "
288              "Send() rather than this one.  Clients that use this form of Send "
289              "will prevent listeners from being able to register to receive "
290              "notices "
291              "based on the sender of the notice. "
292              "ONLY listeners that registered globally will get the notice. ")
293         ;
294 
295     const char* Listener_string = ""
296     "Represents the Notice connection between "
297     "senders and receivers of notices.  When a Listener object expires the "
298     "connection is broken. "
299     "You can also use the Revoke() function to break the connection. "
300     "A Listener object is returned from the Register() and  "
301     "RegisterGlobally() functions. ";
302     class_<Tf_PyNoticeInternal::Listener,
303            boost::noncopyable>("Listener", Listener_string, no_init)
304         .def("Revoke", &Tf_PyNoticeInternal::Listener::Revoke,
305             "Revoke() \n\n"
306             "Revoke interest by a notice listener. "
307             " "
308             "This function revokes interest in the "
309             "particular notice type and call-back method that its "
310             "Listener object was registered for. "
311         )
312         ;
313 }
314