Reduce CommandHandler::TryAddResponseData compiled code size (#31631)
---------
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/app/CommandHandler.h b/src/app/CommandHandler.h
index 659828c..b90cb82 100644
--- a/src/app/CommandHandler.h
+++ b/src/app/CommandHandler.h
@@ -360,24 +360,7 @@
template <typename CommandData>
CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
- // This method, templated with CommandData, captures all the components needs
- // from CommandData with as little code as possible. This in theory should
- // reduce compiled code size.
- //
- // TODO(#30453): Verify the accuracy of the theory outlined below.
- //
- // Theory on code reduction: Previously, non-essential code was unnecessarily
- // templated, leading to compilation and duplication N times. The lambda
- // function below mitigates this issue by isolating only the code segments
- // that genuinely require templating, thereby minimizing duplicate compiled
- // code.
- ConcreteCommandPath responsePath = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId,
- CommandData::GetCommandId() };
- auto encodeCommandDataClosure = [&](TLV::TLVWriter & writer) -> CHIP_ERROR {
- return DataModel::Encode(writer, TLV::ContextTag(CommandDataIB::Tag::kFields), aData);
- };
- return TryAddingResponse(
- [&]() -> CHIP_ERROR { return TryAddResponseData(aRequestCommandPath, responsePath, encodeCommandDataClosure); });
+ return TryAddingResponse([&]() -> CHIP_ERROR { return TryAddResponseData(aRequestCommandPath, aData); });
}
/**
@@ -567,18 +550,21 @@
CHIP_ERROR AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
/**
- * If this function fails, it may leave our TLV buffer in an inconsistent state. Callers should snapshot as needed before
- * calling this function, and roll back as needed afterward.
+ * Non-templated function called before DataModel::Encode when attempting to add a response,
+ * which does all the work needed before encoding the actual type-dependent data into the buffer.
*
- * @param [in] aRequestCommandPath the concrete path of the command we are
- * responding to.
- * @param [in] aResponseCommandPath the concrete command response path.
- * @param [in] encodeCommandDataFunction A lambda function responsible for
- * encoding the CommandData field.
+ * **Important:** If this function fails, the TLV buffer may be left in an inconsistent state.
+ * Callers should create snapshots as necessary before invoking this function and implement
+ * rollback mechanisms if needed.
+ *
+ * **Usage:** This function is intended to be called exclusively by TryAddResponseData. It was
+ * factored out to optimize code size.
+ *
+ * @param aRequestCommandPath The concrete path of the command being responded to.
+ * @param aResponseCommandPath The concrete path of the command response.
*/
- template <typename Function>
- CHIP_ERROR TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, const ConcreteCommandPath & aResponseCommandPath,
- Function && encodeCommandDataFunction)
+ CHIP_ERROR TryAddResponseDataPreEncode(const ConcreteCommandPath & aRequestCommandPath,
+ const ConcreteCommandPath & aResponseCommandPath)
{
// Return early in case of requests targeted to a group, since they should not add a response.
VerifyOrReturnValue(!IsGroupRequest(), CHIP_NO_ERROR);
@@ -587,11 +573,39 @@
prepareParams.SetStartOrEndDataStruct(false);
ScopedChange<bool> internalCallToAddResponse(mInternalCallToAddResponseData, true);
- ReturnErrorOnFailure(PrepareInvokeResponseCommand(aResponseCommandPath, prepareParams));
+ return PrepareInvokeResponseCommand(aResponseCommandPath, prepareParams);
+ }
+
+ // TODO(#31627): It would be awesome if we could remove this template all together.
+ /**
+ * If this function fails, it may leave our TLV buffer in an inconsistent state.
+ * Callers should snapshot as needed before calling this function, and roll back
+ * as needed afterward.
+ *
+ * @param [in] aRequestCommandPath the concrete path of the command we are
+ * responding to.
+ * @param [in] aData the data for the response.
+ */
+ template <typename CommandData>
+ CHIP_ERROR TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
+ {
+ // This method, templated with CommandData, captures all the components needs
+ // from CommandData with as little code as possible.
+ //
+ // Previously, non-essential code was unnecessarily templated, leading to
+ // compilation and duplication N times. By isolating only the code segments
+ // that genuinely require templating, minimizes duplicate compiled code.
+ ConcreteCommandPath responseCommandPath = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId,
+ CommandData::GetCommandId() };
+ ReturnErrorOnFailure(TryAddResponseDataPreEncode(aRequestCommandPath, responseCommandPath));
TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
- ReturnErrorOnFailure(encodeCommandDataFunction(*writer));
+ ReturnErrorOnFailure(DataModel::Encode(*writer, TLV::ContextTag(CommandDataIB::Tag::kFields), aData));
+ // FinishCommand technically should be refactored out as it is not a command that needs templating.
+ // But, because there is only a single function call, keeping it here takes less code. If there is
+ // ever more code between DataModel::Encode and the end of this function, it should be broken out into
+ // TryAddResponseDataPostEncode.
return FinishCommand(/* aEndDataStruct = */ false);
}