From af4fbc1f8a99a3c5370cb6db45435e67f9ce92d2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 18 Jun 2022 11:57:10 -0400 Subject: DRTVWR-564: WIP: Add LazyEventAPI and tests. Tests don't yet pass. LazyEventAPI is a registrar that implicitly instantiates some particular LLEventAPI subclass on demand: that is, when LLEventPumps::obtain() tries to find an LLEventPump by the registered name. This leverages the new LLEventPumps::registerPumpFactory() machinery. Fix registerPumpFactory() to adapt the passed PumpFactory to accept TypeFactory parameters (two of which it ignores). Supplement it with unregisterPumpFactory() to support LazyEventAPI instances with lifespans shorter than the process -- which may be mostly test programs, but still a hole worth closing. Similarly, add unregisterTypeFactory(). A LazyEventAPI subclass takes over responsibility for specifying the LLEventAPI's name, desc, field, plus whatever add() calls will be needed to register the LLEventAPI's operations. This is so we can (later) enhance LLLeapListener to consult LazyEventAPI instances for not-yet-instantiated LLEventAPI metadata, as well as enumerating existing LLEventAPI instances. The trickiest part of this is capturing calls to the various LLEventDispatcher::add() overloads in such a way that, when the LLEventAPI subclass is eventually instantiated, we can replay them in the new instance. LLEventAPI acquires a new protected constructor specifically for use by a subclass registered by a companion LazyEventAPI. It accepts a const reference to LazyEventAPIParams, intended to be opaque to the LLEventAPI subclass; the subclass must declare a constructor that accepts and forwards the parameter block to the new LLEventAPI constructor. The implementation delegates to the existing LLEventAPI constructor, plus it runs deferred add() calls. LLDispatchListener now derives from LLEventStream instead of containing it as a data member. The reason is that if LLEventPumps::obtain() implicitly instantiates it, LLEventPumps's destructor will try to destroy it by deleting the LLEventPump*. If the LLEventPump returned by the factory function is a data member of an outer class, that won't work so well. But if LLDispatchListener (and by implication, LLEventAPI and any subclass) is derived from LLEventPump, then the virtual destructor will Do The Right Thing. Change LLDispatchListener to *not* allow tweaking the LLEventPump name. Since the overwhelming use case for LLDispatchListener is LLEventAPI, accepting but silently renaming an LLEventAPI subclass would ensure nobody could reach it. Change LLEventDispatcher's use of std::enable_if to control the set of add() overloads available for the intended use cases. Apparently this formulation is just as functional at the method declaration point, while avoiding the need to restate the whole enable_if expression at the method definition point. Add lazyeventapi_test.cpp to exercise. --- indra/llcommon/tests/lazyeventapi_test.cpp | 89 ++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 indra/llcommon/tests/lazyeventapi_test.cpp (limited to 'indra/llcommon/tests/lazyeventapi_test.cpp') diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp new file mode 100644 index 0000000000..6639c5e540 --- /dev/null +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -0,0 +1,89 @@ +/** + * @file lazyeventapi_test.cpp + * @author Nat Goodspeed + * @date 2022-06-18 + * @brief Test for lazyeventapi. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lazyeventapi.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llevents.h" + +// LLEventAPI listener subclass +class MyListener: public LLEventAPI +{ +public: + MyListener(const LL::LazyEventAPIParams& params): + LLEventAPI(params) + {} + + void get(const LLSD& event) + { + std::cout << "MyListener::get() got " << event << std::endl; + } +}; + +// LazyEventAPI registrar subclass +class MyRegistrar: public LL::LazyEventAPI +{ + using super = LL::LazyEventAPI; + using super::listener; +public: + MyRegistrar(): + super("Test", "This is a test LLEventAPI") + { + add("get", "This is a get operation", &listener::get); + } +}; +// Normally we'd declare a static instance of MyRegistrar -- but because we +// may want to test with and without, defer declaration to individual test +// methods. + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct lazyeventapi_data + { + ~lazyeventapi_data() + { + // after every test, reset LLEventPumps + LLEventPumps::deleteSingleton(); + } + }; + typedef test_group lazyeventapi_group; + typedef lazyeventapi_group::object object; + lazyeventapi_group lazyeventapigrp("lazyeventapi"); + + template<> template<> + void object::test<1>() + { + set_test_name("LazyEventAPI"); + // this is where the magic (should) happen + // 'register' still a keyword until C++17 + MyRegistrar regster; + LLEventPumps::instance().obtain("Test").post("hey"); + } + + template<> template<> + void object::test<2>() + { + set_test_name("No LazyEventAPI"); + // Because the MyRegistrar declaration in test<1>() is local, because + // it has been destroyed, we fully expect NOT to reach a MyListener + // instance with this post. + LLEventPumps::instance().obtain("Test").post("moot"); + } +} // namespace tut -- cgit v1.2.3 From fdc0257acbde5a2d5bb201efcc8bb723df09daf8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 15:23:29 -0400 Subject: DRTVWR-564: Fix LLEventDispatcher::addMethod() for LazyEventAPI. A classic LLEventAPI subclass calls LLEventDispatcher::add() methods in its own constructor. At that point, addMethod() can reliably dynamic_cast its 'this' pointer to the new subclass. But because of the way LazyEventAPI queues up add() calls, they're invoked in the (new) LLEventAPI constructor itself. The subclass constructor body hasn't even started running, and LLEventDispatcher::addMethod()'s dynamic_cast to the LLEventAPI subclass returns nullptr. addMethod() claims the new subclass isn't derived from LLEventDispatcher, which is confusing since it is. It works to change addMethod()'s dynamic_cast to static_cast. Flesh out lazyeventapi_test.cpp. post() maps with "op" keys to actually try to engage the registered operation. Give the operation an observable side effect; use ensure_mumble() to verify. Also verify that LazyEventAPI has captured the subject LLEventAPI's metadata in a way we can retrieve. --- indra/llcommon/tests/lazyeventapi_test.cpp | 47 ++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) (limited to 'indra/llcommon/tests/lazyeventapi_test.cpp') diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index 6639c5e540..4c78fd7105 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -19,18 +19,25 @@ // other Linden headers #include "../test/lltut.h" #include "llevents.h" +#include "llsdutil.h" + +// observable side effect, solely for testing +static LLSD data; // LLEventAPI listener subclass class MyListener: public LLEventAPI { public: + // need this trivial forwarding constructor + // (of course do any other initialization your subclass requires) MyListener(const LL::LazyEventAPIParams& params): LLEventAPI(params) {} - void get(const LLSD& event) + // example operation, registered by LazyEventAPI subclass below + void set_data(const LLSD& event) { - std::cout << "MyListener::get() got " << event << std::endl; + data = event["data"]; } }; @@ -40,14 +47,17 @@ class MyRegistrar: public LL::LazyEventAPI using super = LL::LazyEventAPI; using super::listener; public: + // LazyEventAPI subclass initializes like a classic LLEventAPI subclass + // constructor, with API name and desc plus add() calls for the defined + // operations MyRegistrar(): super("Test", "This is a test LLEventAPI") { - add("get", "This is a get operation", &listener::get); + add("set", "This is a set operation", &listener::set_data); } }; // Normally we'd declare a static instance of MyRegistrar -- but because we -// may want to test with and without, defer declaration to individual test +// want to test both with and without, defer declaration to individual test // methods. /***************************************************************************** @@ -57,6 +67,11 @@ namespace tut { struct lazyeventapi_data { + lazyeventapi_data() + { + // before every test, reset 'data' + data.clear(); + } ~lazyeventapi_data() { // after every test, reset LLEventPumps @@ -74,7 +89,8 @@ namespace tut // this is where the magic (should) happen // 'register' still a keyword until C++17 MyRegistrar regster; - LLEventPumps::instance().obtain("Test").post("hey"); + LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey")); + ensure_equals("failed to set data", data.asString(), "hey"); } template<> template<> @@ -84,6 +100,25 @@ namespace tut // Because the MyRegistrar declaration in test<1>() is local, because // it has been destroyed, we fully expect NOT to reach a MyListener // instance with this post. - LLEventPumps::instance().obtain("Test").post("moot"); + LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot")); + ensure("accidentally set data", ! data.isDefined()); + } + + template<> template<> + void object::test<3>() + { + set_test_name("LazyEventAPI metadata"); + MyRegistrar regster; + const MyRegistrar* found = nullptr; + for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot()) + if ((found = dynamic_cast(®istrar))) + break; + ensure("Failed to find MyRegistrar via LLInstanceTracker", found); + ensure_equals("wrong API name", found->mParams.name, "Test"); + ensure_contains("wrong API desc", found->mParams.desc, "test LLEventAPI"); + ensure_equals("wrong API field", found->mParams.field, "op"); + ensure_equals("failed to find operations", found->mOperations.size(), 1); + ensure_equals("wrong operation name", found->mOperations[0].first, "set"); + ensure_contains("wrong operation desc", found->mOperations[0].second, "set operation"); } } // namespace tut -- cgit v1.2.3 From 6b53036f7499a4e42813378009050eaf02c0b69d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 22 Jun 2022 10:51:11 -0400 Subject: DRTVWR-564: Allow LLLeapListener to report LazyEventAPIs too. One important factor in the design of LazyEventAPI was the desire to allow LLLeapListener to query metadata for an LLEventAPI even if it hasn't yet been instantiated by LazyEventAPI. That's why LazyEventAPI requires the same metadata required by a classic LLEventAPI. Instead of just publicly exposing its data members, give LazyEventAPI a query API mimicking LLEventAPI / LLEventDispatcher. Protect data members and private methods. Adapt lazyeventapi_test.cpp accordingly. Extend LLLeapListener::getAPIs() and getAPI() to look through LazyEventAPIBase instances after first checking existing LLEventAPI instances. Because the query API for LazyEventAPIBase mimics LLEventAPI's, extract getAPI()'s actual metadata reporting to a new internal template function reportAPI(). While we're touching LLLeapListener, we no longer need BOOST_FOREACH(). --- indra/llcommon/tests/lazyeventapi_test.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'indra/llcommon/tests/lazyeventapi_test.cpp') diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index 4c78fd7105..31b2d6d17f 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -109,16 +109,28 @@ namespace tut { set_test_name("LazyEventAPI metadata"); MyRegistrar regster; + // Of course we have 'regster' in hand; we don't need to search for + // it. But this next test verifies that we can find (all) LazyEventAPI + // instances using LazyEventAPIBase::instance_snapshot. Normally we + // wouldn't search; normally we'd just look at each instance in the + // loop body. const MyRegistrar* found = nullptr; for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot()) if ((found = dynamic_cast(®istrar))) break; ensure("Failed to find MyRegistrar via LLInstanceTracker", found); - ensure_equals("wrong API name", found->mParams.name, "Test"); - ensure_contains("wrong API desc", found->mParams.desc, "test LLEventAPI"); - ensure_equals("wrong API field", found->mParams.field, "op"); - ensure_equals("failed to find operations", found->mOperations.size(), 1); - ensure_equals("wrong operation name", found->mOperations[0].first, "set"); - ensure_contains("wrong operation desc", found->mOperations[0].second, "set operation"); + + ensure_equals("wrong API name", found->getName(), "Test"); + ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI"); + ensure_equals("wrong API field", found->getDispatchKey(), "op"); + // Normally we'd just iterate over *found. But for test purposes, + // actually capture the range of NameDesc pairs in a vector. + std::vector ops{ found->begin(), found->end() }; + ensure_equals("failed to find operations", ops.size(), 1); + ensure_equals("wrong operation name", ops[0].first, "set"); + ensure_contains("wrong operation desc", ops[0].second, "set operation"); + LLSD metadata{ found->getMetadata(ops[0].first) }; + ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first); + ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second); } } // namespace tut -- cgit v1.2.3