/** * @file classic_callback_test.cpp * @author Nat Goodspeed * @date 2021-09-22 * @brief Test ClassicCallback and HeapClassicCallback. * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Copyright (c) 2021, Linden Research, Inc. * $/LicenseInfo$ */ // Precompiled header #include "linden_common.h" // associated header #include "classic_callback.h" // STL headers #include <iostream> #include <string> // std headers // external library headers // other Linden headers #include "../test/lltut.h" /***************************************************************************** * example callback *****************************************************************************/ // callback_t is part of the specification of someAPI() typedef void (*callback_t)(const char*, void*); void someAPI(callback_t callback, void* userdata) { callback("called", userdata); } // C++ callable I want as the actual callback struct MyCallback { void operator()(const char* msg, void*) { mMsg = msg; } void callback_with_extra(const std::string& extra, const char* msg) { mMsg = extra + ' ' + msg; } std::string mMsg; }; /***************************************************************************** * example callback accepting several params, and void* userdata isn't first *****************************************************************************/ typedef std::string (*complex_callback)(int, const char*, void*, double); std::string otherAPI(complex_callback callback, void* userdata) { return callback(17, "hello world", userdata, 3.0); } // struct into which we can capture complex_callback params static struct Data { void set(int i, const char* s, double f) { mi = i; ms = s; mf = f; } void clear() { set(0, "", 0.0); } int mi; std::string ms; double mf; } sData; // C++ callable I want to pass struct OtherCallback { std::string operator()(int num, const char* str, void*, double approx) { sData.set(num, str, approx); return "hello back!"; } }; /***************************************************************************** * TUT *****************************************************************************/ namespace tut { struct classic_callback_data { }; typedef test_group<classic_callback_data> classic_callback_group; typedef classic_callback_group::object object; classic_callback_group classic_callbackgrp("classic_callback"); template<> template<> void object::test<1>() { set_test_name("ClassicCallback"); // engage someAPI(MyCallback()) auto ccb{ makeClassicCallback<callback_t>(MyCallback()) }; someAPI(ccb.get_callback(), ccb.get_userdata()); // Unfortunately, with the side effect confined to the bound // MyCallback instance, that call was invisible. Bind a reference to a // named instance by specifying a ref type. MyCallback mcb; ClassicCallback<callback_t, void*, MyCallback&> ccb2(mcb); someAPI(ccb2.get_callback(), ccb2.get_userdata()); ensure_equals("failed to call through ClassicCallback", mcb.mMsg, "called"); // try with HeapClassicCallback mcb.mMsg.clear(); auto hcbp{ makeHeapClassicCallback<callback_t>(mcb) }; someAPI(hcbp->get_callback(), hcbp->get_userdata()); ensure_equals("failed to call through HeapClassicCallback", mcb.mMsg, "called"); // lambda // The tricky thing here is that a lambda is an unspecified type, so // you can't declare a ClassicCallback<signature, void*, that type>. mcb.mMsg.clear(); auto xcb( makeClassicCallback<callback_t>( [&mcb](const char* msg, void*) { mcb.callback_with_extra("extra", msg); })); someAPI(xcb.get_callback(), xcb.get_userdata()); ensure_equals("failed to call lambda", mcb.mMsg, "extra called"); // engage otherAPI(OtherCallback()) OtherCallback ocb; // Instead of specifying a reference type for the bound CALLBACK, as // with ccb2 above, you can alternatively move the callable object // into the ClassicCallback (of course AFTER any other reference). // That's why OtherCallback uses external data for its observable side // effect. auto occb{ makeClassicCallback<complex_callback>(std::move(ocb)) }; std::string result{ otherAPI(occb.get_callback(), occb.get_userdata()) }; ensure_equals("failed to return callback result", result, "hello back!"); ensure_equals("failed to set int", sData.mi, 17); ensure_equals("failed to set string", sData.ms, "hello world"); ensure_equals("failed to set double", sData.mf, 3.0); } } // namespace tut