Result<Tileset> operator()(const V& value) const { Tileset result; auto tiles = objectMember(value, "tiles"); if (!tiles) { return Error { "source must have tiles" }; } if (!isArray(*tiles)) { return Error { "source tiles must be an array" }; } for (std::size_t i = 0; i < arrayLength(*tiles); i++) { optional<std::string> urlTemplate = toString(arrayMember(*tiles, i)); if (!urlTemplate) { return Error { "source tiles member must be a string" }; } result.tiles.push_back(std::move(*urlTemplate)); } auto schemeValue = objectMember(value, "scheme"); if (schemeValue) { optional<std::string> scheme = toString(*schemeValue); if (scheme && *scheme == "tms") { result.scheme = Tileset::Scheme::TMS; } } auto minzoomValue = objectMember(value, "minzoom"); if (minzoomValue) { optional<float> minzoom = toNumber(*minzoomValue); if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits<uint8_t>::max()) { return Error { "invalid minzoom" }; } result.zoomRange.min = *minzoom; } auto maxzoomValue = objectMember(value, "maxzoom"); if (maxzoomValue) { optional<float> maxzoom = toNumber(*maxzoomValue); if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits<uint8_t>::max()) { return Error { "invalid maxzoom" }; } result.zoomRange.max = *maxzoom; } auto attributionValue = objectMember(value, "attribution"); if (attributionValue) { optional<std::string> attribution = toString(*attributionValue); if (!attribution) { return Error { "source attribution must be a string" }; } result.attribution = std::move(*attribution); } return result; }
Result<Function<T>> operator()(const V& value) const { if (!isObject(value)) { return Error { "function must be an object" }; } auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { return Error { "function value must specify stops" }; } if (!isArray(*stopsValue)) { return Error { "function stops must be an array" }; } if (arrayLength(*stopsValue) == 0) { return Error { "function must have at least one stop" }; } std::vector<std::pair<float, T>> stops; for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { const auto& stopValue = arrayMember(*stopsValue, i); if (!isArray(stopValue)) { return Error { "function stop must be an array" }; } if (arrayLength(stopValue) != 2) { return Error { "function stop must have two elements" }; } optional<float> z = toNumber(arrayMember(stopValue, 0)); if (!z) { return Error { "function stop zoom level must be a number" }; } Result<T> v = convert<T>(arrayMember(stopValue, 1)); if (!v) { return v.error(); } stops.emplace_back(*z, *v); } auto baseValue = objectMember(value, "base"); if (!baseValue) { return Function<T>(stops, 1.0f); } optional<float> base = toNumber(*baseValue); if (!base) { return Error { "function base must be a number"}; } return Function<T>(stops, *base); }
optional<variant<Ts...>> operator()(const V& value, Error& error) const { std::string type = util::Interpolatable<T>::value ? "exponential" : "interval"; auto typeValue = objectMember(value, "type"); if (typeValue && toString(*typeValue)) { type = *toString(*typeValue); } bool matched = false; optional<variant<Ts...>> result; // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47226 auto tryConvert = [&] (auto* tp) { using Stops = std::decay_t<decltype(*tp)>; if (type == Converter<Stops>::type) { matched = true; optional<Stops> stops = convert<Stops>(value, error); if (stops) { result = variant<Ts...>(*stops); } } }; util::ignore({ (tryConvert((Ts*)nullptr), 0)... }); if (!matched) { error = { "unsupported function type" }; return {}; } return result; }
optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::operator()(const Convertible& value, Error& error, const std::string& id) const { if (!isObject(value)) { error = { "source must be an object" }; return {}; } auto typeValue = objectMember(value, "type"); if (!typeValue) { error = { "source must have a type" }; return {}; } optional<std::string> type = toString(*typeValue); if (!type) { error = { "source type must be a string" }; return {}; } if (*type == "raster") { return convertRasterSource(id, value, error); } else if (*type == "vector") { return convertVectorSource(id, value, error); } else if (*type == "geojson") { return convertGeoJSONSource(id, value, error); } else if (*type == "image") { return convertImageSource(id, value, error); } else { error = { "invalid source type" }; return {}; } }
static optional<std::unique_ptr<Source>> convertGeoJSONSource(const std::string& id, const Convertible& value, Error& error) { auto dataValue = objectMember(value, "data"); if (!dataValue) { error = { "GeoJSON source must have a data value" }; return {}; } optional<GeoJSONOptions> options = convert<GeoJSONOptions>(value, error); if (!options) { return {}; } auto result = std::make_unique<GeoJSONSource>(id, *options); if (isObject(*dataValue)) { optional<GeoJSON> geoJSON = convert<GeoJSON>(*dataValue, error); if (!geoJSON) { return {}; } result->setGeoJSON(std::move(*geoJSON)); } else if (toString(*dataValue)) { result->setURL(*toString(*dataValue)); } else { error = { "GeoJSON data must be a URL or an object" }; return {}; } return { std::move(result) }; }
optional<Error> setPaintProperties(Layer& layer, const V& value) { auto paintValue = objectMember(value, "paint"); if (!paintValue) { return {}; } return eachMember(*paintValue, [&] (const std::string& k, const V& v) { return setPaintProperty(layer, k, v); }); }
optional<Error> setPaintProperties(Layer& layer, const Convertible& value) { auto paintValue = objectMember(value, "paint"); if (!paintValue) { return nullopt; } if (!isObject(*paintValue)) { return { { "paint must be an object" } }; } return eachMember(*paintValue, [&] (const std::string& k, const Convertible& v) { return layer.setPaintProperty(k, v); }); }
static optional<std::unique_ptr<Source>> convertImageSource(const std::string& id, const Convertible& value, Error& error) { auto urlValue = objectMember(value, "url"); if (!urlValue) { error = { "Image source must have a url value" }; return {}; } auto urlString = toString(*urlValue); if (!urlString) { error = { "Image url must be a URL string" }; return {}; } auto coordinatesValue = objectMember(value, "coordinates"); if (!coordinatesValue) { error = { "Image source must have a coordinates values" }; return {}; } if (!isArray(*coordinatesValue) || arrayLength(*coordinatesValue) != 4) { error = { "Image coordinates must be an array of four longitude latitude pairs" }; return {}; } std::array<LatLng, 4> coordinates; for (std::size_t i=0; i < 4; i++) { auto latLng = conversion::convert<LatLng>(arrayMember(*coordinatesValue,i), error); if (!latLng) { return {}; } coordinates[i] = *latLng; } auto result = std::make_unique<ImageSource>(id, coordinates); result->setURL(*urlString); return { std::move(result) }; }
optional<std::unique_ptr<Layer>> convertVectorLayer(const std::string& id, const V& value, Error& error) const { auto sourceValue = objectMember(value, "source"); if (!sourceValue) { error = { "layer must have a source" }; return {}; } optional<std::string> source = toString(*sourceValue); if (!source) { error = { "layer source must be a string" }; return {}; } std::unique_ptr<LayerType> layer = std::make_unique<LayerType>(id, *source); auto sourceLayerValue = objectMember(value, "source-layer"); if (sourceLayerValue) { optional<std::string> sourceLayer = toString(*sourceLayerValue); if (!sourceLayer) { error = { "layer source-layer must be a string" }; return {}; } layer->setSourceLayer(*sourceLayer); } auto filterValue = objectMember(value, "filter"); if (filterValue) { optional<Filter> filter = convert<Filter>(*filterValue, error); if (!filter) { return {}; } layer->setFilter(*filter); } return { std::move(layer) }; }
optional<std::unique_ptr<Layer>> convertRasterLayer(const std::string& id, const V& value, Error& error) const { auto sourceValue = objectMember(value, "source"); if (!sourceValue) { error = { "layer must have a source" }; return {}; } optional<std::string> source = toString(*sourceValue); if (!source) { error = { "layer source must be a string" }; return {}; } return { std::make_unique<RasterLayer>(id, *source) }; }
optional<std::map<D, R>> convertStops(const V& value, Error& error) { auto stopsValue = objectMember(value, "stops"); if (!stopsValue) { error = { "function value must specify stops" }; return {}; } if (!isArray(*stopsValue)) { error = { "function stops must be an array" }; return {}; } if (arrayLength(*stopsValue) == 0) { error = { "function must have at least one stop" }; return {}; } std::map<D, R> stops; for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { const auto& stopValue = arrayMember(*stopsValue, i); if (!isArray(stopValue)) { error = { "function stop must be an array" }; return {}; } if (arrayLength(stopValue) != 2) { error = { "function stop must have two elements" }; return {}; } optional<D> d = convert<D>(arrayMember(stopValue, 0), error); if (!d) { return {}; } optional<R> r = convert<R>(arrayMember(stopValue, 1), error); if (!r) { return {}; } stops.emplace(*d, *r); } return stops; }
// A tile source can either specify a URL to TileJSON, or inline TileJSON. static optional<variant<std::string, Tileset>> convertURLOrTileset(const Convertible& value, Error& error) { auto urlVal = objectMember(value, "url"); if (!urlVal) { optional<Tileset> tileset = convert<Tileset>(value, error); if (!tileset) { return {}; } return { *tileset }; } optional<std::string> url = toString(*urlVal); if (!url) { error = { "source url must be a string" }; return {}; } return { *url }; }
optional<ExponentialStops<T>> operator()(const V& value, Error& error) const { auto stops = convertStops<float, T>(value, error); if (!stops) { return {}; } auto baseValue = objectMember(value, "base"); if (!baseValue) { return ExponentialStops<T>(*stops); } optional<float> base = toNumber(*baseValue); if (!base) { error = { "function base must be a number"}; return {}; } return ExponentialStops<T>(*stops, *base); }
static optional<std::unique_ptr<Source>> convertRasterSource(const std::string& id, const Convertible& value, Error& error) { optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error); if (!urlOrTileset) { return {}; } uint16_t tileSize = util::tileSize; auto tileSizeValue = objectMember(value, "tileSize"); if (tileSizeValue) { optional<float> size = toNumber(*tileSizeValue); if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) { error = { "invalid tileSize" }; return {}; } tileSize = *size; } return { std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize) }; }
optional<optional<T>> convertDefaultValue(const V& value, Error& error) { auto defaultValueValue = objectMember(value, "default"); if (!defaultValueValue) { return optional<T>(); } auto defaultValue = convert<T>(*defaultValueValue, error); if (!defaultValue) { error = { R"(wrong type for "default": )" + error.message }; return {}; } return { *defaultValue }; } template <class T> struct Converter<SourceFunction<T>> { template <class V> optional<SourceFunction<T>> operator()(const V& value, Error& error) const { if (!isObject(value)) { error = { "function must be an object" }; return {}; }
optional<PropertyExpression<T>> convertFunctionToExpression(const Convertible& value, Error& error, bool convertTokens) { auto expression = convertFunctionToExpression(expression::valueTypeToExpressionType<T>(), value, error, convertTokens); if (!expression) { return nullopt; } optional<T> defaultValue{}; auto defaultValueValue = objectMember(value, "default"); if (defaultValueValue) { defaultValue = convert<T>(*defaultValueValue, error); if (!defaultValue) { error.message = R"(wrong type for "default": )" + error.message; return nullopt; } } return PropertyExpression<T>(std::move(*expression), defaultValue); } template optional<PropertyExpression<AlignmentType>> convertFunctionToExpression<AlignmentType>(const Convertible&, Error&, bool); template optional<PropertyExpression<bool>> convertFunctionToExpression<bool>(const Convertible&, Error&, bool); template optional<PropertyExpression<CirclePitchScaleType>> convertFunctionToExpression<CirclePitchScaleType>(const Convertible&, Error&, bool); template optional<PropertyExpression<float>> convertFunctionToExpression<float>(const Convertible&, Error&, bool); template optional<PropertyExpression<HillshadeIlluminationAnchorType>> convertFunctionToExpression<HillshadeIlluminationAnchorType>(const Convertible&, Error&, bool); template optional<PropertyExpression<IconTextFitType>> convertFunctionToExpression<IconTextFitType>(const Convertible&, Error&, bool); template optional<PropertyExpression<LightAnchorType>> convertFunctionToExpression<LightAnchorType>(const Convertible&, Error&, bool); template optional<PropertyExpression<LineCapType>> convertFunctionToExpression<LineCapType>(const Convertible&, Error&, bool); template optional<PropertyExpression<LineJoinType>> convertFunctionToExpression<LineJoinType>(const Convertible&, Error&, bool); template optional<PropertyExpression<Color>> convertFunctionToExpression<Color>(const Convertible&, Error&, bool); template optional<PropertyExpression<Position>> convertFunctionToExpression<Position>(const Convertible&, Error&, bool); template optional<PropertyExpression<RasterResamplingType>> convertFunctionToExpression<RasterResamplingType>(const Convertible&, Error&, bool); template optional<PropertyExpression<std::array<float, 2>>> convertFunctionToExpression<std::array<float, 2>>(const Convertible&, Error&, bool); template optional<PropertyExpression<std::array<float, 4>>> convertFunctionToExpression<std::array<float, 4>>(const Convertible&, Error&, bool); template optional<PropertyExpression<std::string>> convertFunctionToExpression<std::string>(const Convertible&, Error&, bool); template optional<PropertyExpression<std::vector<float>>> convertFunctionToExpression<std::vector<float>>(const Convertible&, Error&, bool); template optional<PropertyExpression<std::vector<std::string>>> convertFunctionToExpression<std::vector<std::string>>(const Convertible&, Error&, bool); template optional<PropertyExpression<SymbolAnchorType>> convertFunctionToExpression<SymbolAnchorType>(const Convertible&, Error&, bool); template optional<PropertyExpression<SymbolPlacementType>> convertFunctionToExpression<SymbolPlacementType>(const Convertible&, Error&, bool); template optional<PropertyExpression<SymbolZOrderType>> convertFunctionToExpression<SymbolZOrderType>(const Convertible&, Error&, bool); template optional<PropertyExpression<TextJustifyType>> convertFunctionToExpression<TextJustifyType>(const Convertible&, Error&, bool); template optional<PropertyExpression<TextTransformType>> convertFunctionToExpression<TextTransformType>(const Convertible&, Error&, bool); template optional<PropertyExpression<TranslateAnchorType>> convertFunctionToExpression<TranslateAnchorType>(const Convertible&, Error&, bool); template optional<PropertyExpression<Formatted>> convertFunctionToExpression<Formatted>(const Convertible&, Error&, bool); // Ad-hoc Converters for double and int64_t. We should replace float with double wholesale, // and promote the int64_t Converter to general use (and it should check that the input is // an integer). template <> struct Converter<double> { optional<double> operator()(const Convertible& value, Error& error) const { auto converted = convert<float>(value, error); if (!converted) { return nullopt; } return *converted; } }; template <> struct Converter<int64_t> { optional<int64_t> operator()(const Convertible& value, Error& error) const { auto converted = convert<float>(value, error); if (!converted) { return nullopt; } return *converted; } }; enum class FunctionType { Interval, Exponential, Categorical, Identity, Invalid }; static bool interpolatable(type::Type type) { return type.match( [&] (const type::NumberType&) { return true; }, [&] (const type::ColorType&) { return true; }, [&] (const type::Array& array) { return array.N && array.itemType == type::Number; }, [&] (const auto&) { return false; } ); } static optional<std::unique_ptr<Expression>> convertLiteral(type::Type type, const Convertible& value, Error& error, bool convertTokens = false) { return type.match( [&] (const type::NumberType&) -> optional<std::unique_ptr<Expression>> { auto result = convert<float>(value, error); if (!result) { return nullopt; } return literal(double(*result)); }, [&] (const type::BooleanType&) -> optional<std::unique_ptr<Expression>> { auto result = convert<bool>(value, error); if (!result) { return nullopt; } return literal(*result); }, [&] (const type::StringType&) -> optional<std::unique_ptr<Expression>> { auto result = convert<std::string>(value, error); if (!result) { return nullopt; } return convertTokens ? convertTokenStringToExpression(*result) : literal(*result); }, [&] (const type::ColorType&) -> optional<std::unique_ptr<Expression>> { auto result = convert<Color>(value, error); if (!result) { return nullopt; } return literal(*result); }, [&] (const type::Array& array) -> optional<std::unique_ptr<Expression>> { if (!isArray(value)) { error.message = "value must be an array"; return nullopt; } if (array.N && arrayLength(value) != *array.N) { error.message = "value must be an array of length " + util::toString(*array.N); return nullopt; } return array.itemType.match( [&] (const type::NumberType&) -> optional<std::unique_ptr<Expression>> { std::vector<expression::Value> result; result.reserve(arrayLength(value)); for (std::size_t i = 0; i < arrayLength(value); ++i) { optional<float> number = toNumber(arrayMember(value, i)); if (!number) { error.message = "value must be an array of numbers"; return nullopt; } result.push_back(double(*number)); } return literal(result); }, [&] (const type::StringType&) -> optional<std::unique_ptr<Expression>> { std::vector<expression::Value> result; result.reserve(arrayLength(value)); for (std::size_t i = 0; i < arrayLength(value); ++i) { optional<std::string> string = toString(arrayMember(value, i)); if (!string) { error.message = "value must be an array of strings"; return nullopt; } result.push_back(*string); } return literal(result); }, [&] (const auto&) -> optional<std::unique_ptr<Expression>> { assert(false); // No properties use this type. return nullopt; } ); },
optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(const Convertible& value, Error& error) const { if (!isObject(value)) { error.message = "layer must be an object"; return nullopt; } auto idValue = objectMember(value, "id"); if (!idValue) { error.message = "layer must have an id"; return nullopt; } optional<std::string> id = toString(*idValue); if (!id) { error.message = "layer id must be a string"; return nullopt; } auto typeValue = objectMember(value, "type"); if (!typeValue) { error.message = "layer must have a type"; return nullopt; } optional<std::string> type = toString(*typeValue); if (!type) { error.message = "layer type must be a string"; return nullopt; } std::unique_ptr<Layer> layer = LayerManager::get()->createLayer(*type, *id, value, error); if (!layer) { return nullopt; } auto minzoomValue = objectMember(value, "minzoom"); if (minzoomValue) { optional<float> minzoom = toNumber(*minzoomValue); if (!minzoom) { error.message = "minzoom must be numeric"; return nullopt; } layer->setMinZoom(*minzoom); } auto maxzoomValue = objectMember(value, "maxzoom"); if (maxzoomValue) { optional<float> maxzoom = toNumber(*maxzoomValue); if (!maxzoom) { error.message = "maxzoom must be numeric"; return nullopt; } layer->setMaxZoom(*maxzoom); } auto layoutValue = objectMember(value, "layout"); if (layoutValue) { if (!isObject(*layoutValue)) { error.message = "layout must be an object"; return nullopt; } optional<Error> error_ = eachMember(*layoutValue, [&] (const std::string& k, const Convertible& v) { return layer->setLayoutProperty(k, v); }); if (error_) { error = *error_; return nullopt; } } optional<Error> error_ = setPaintProperties(*layer, value); if (error_) { error = *error_; return nullopt; } return std::move(layer); }
return {}; } return { *defaultValue }; } template <class T> struct Converter<SourceFunction<T>> { template <class V> optional<SourceFunction<T>> operator()(const V& value, Error& error) const { if (!isObject(value)) { error = { "function must be an object" }; return {}; } auto propertyValue = objectMember(value, "property"); if (!propertyValue) { error = { "function must specify property" }; return {}; } auto propertyString = toString(*propertyValue); if (!propertyString) { error = { "function property must be a string" }; return {}; } auto stops = StopsConverter<T, typename SourceFunction<T>::Stops>()(value, error); if (!stops) { return {}; }
optional<std::unique_ptr<Layer>> operator()(const V& value, Error& error) const { if (!isObject(value)) { error = { "layer must be an object" }; return {}; } auto idValue = objectMember(value, "id"); if (!idValue) { error = { "layer must have an id" }; return {}; } optional<std::string> id = toString(*idValue); if (!id) { error = { "layer id must be a string" }; return {}; } auto typeValue = objectMember(value, "type"); if (!typeValue) { error = { "layer must have a type" }; return {}; } optional<std::string> type = toString(*typeValue); if (!type) { error = { "layer type must be a string" }; return {}; } optional<std::unique_ptr<Layer>> converted; if (*type == "fill") { converted = convertVectorLayer<FillLayer>(*id, value, error); } else if (*type == "fill-extrusion") { converted = convertVectorLayer<FillExtrusionLayer>(*id, value, error); } else if (*type == "line") { converted = convertVectorLayer<LineLayer>(*id, value, error); } else if (*type == "circle") { converted = convertVectorLayer<CircleLayer>(*id, value, error); } else if (*type == "symbol") { converted = convertVectorLayer<SymbolLayer>(*id, value, error); } else if (*type == "raster") { converted = convertRasterLayer(*id, value, error); } else if (*type == "background") { converted = convertBackgroundLayer(*id, value, error); } else { error = { "invalid layer type" }; return {}; } if (!converted) { return converted; } std::unique_ptr<Layer> layer = std::move(*converted); auto minzoomValue = objectMember(value, "minzoom"); if (minzoomValue) { optional<float> minzoom = toNumber(*minzoomValue); if (!minzoom) { error = { "minzoom must be numeric" }; return {}; } layer->setMinZoom(*minzoom); } auto maxzoomValue = objectMember(value, "maxzoom"); if (maxzoomValue) { optional<float> maxzoom = toNumber(*maxzoomValue); if (!maxzoom) { error = { "maxzoom must be numeric" }; return {}; } layer->setMaxZoom(*maxzoom); } auto layoutValue = objectMember(value, "layout"); if (layoutValue) { if (!isObject(*layoutValue)) { error = { "layout must be an object" }; return {}; } optional<Error> error_ = eachMember(*layoutValue, [&] (const std::string& k, const V& v) { return setLayoutProperty(*layer, k, v); }); if (error_) { error = *error_; return {}; } } optional<Error> error_ = setPaintProperties(*layer, value); if (error_) { error = *error_; return {}; } return std::move(layer); }
optional<Light> Converter<Light>::operator()(const Convertible& value, Error& error) const { if (!isObject(value)) { error.message = "light must be an object"; return nullopt; } Light light; const auto anchor = objectMember(value, "anchor"); if (anchor) { optional<PropertyValue<LightAnchorType>> convertedAnchor = convert<PropertyValue<LightAnchorType>>(*anchor, error, false, false); if (convertedAnchor) { light.setAnchor(*convertedAnchor); } else { return nullopt; } } const auto anchorTransition = objectMember(value, "anchor-transition"); if (anchorTransition) { optional<TransitionOptions> transition = convert<TransitionOptions>(*anchorTransition, error); if (transition) { light.setAnchorTransition(*transition); } else { return nullopt; } } const auto color = objectMember(value, "color"); if (color) { optional<PropertyValue<Color>> convertedColor = convert<PropertyValue<Color>>(*color, error, false, false); if (convertedColor) { light.setColor(*convertedColor); } else { return nullopt; } } const auto colorTransition = objectMember(value, "color-transition"); if (colorTransition) { optional<TransitionOptions> transition = convert<TransitionOptions>(*colorTransition, error); if (transition) { light.setColorTransition(*transition); } else { return nullopt; } } const auto position = objectMember(value, "position"); if (position) { optional<PropertyValue<Position>> convertedPosition = convert<PropertyValue<Position>>(*position, error, false, false); if (convertedPosition) { light.setPosition(*convertedPosition); } else { return nullopt; } } const auto positionTransition = objectMember(value, "position-transition"); if (positionTransition) { optional<TransitionOptions> transition = convert<TransitionOptions>(*positionTransition, error); if (transition) { light.setPositionTransition(*transition); } else { return nullopt; } } const auto intensity = objectMember(value, "intensity"); if (intensity) { optional<PropertyValue<float>> convertedIntensity = convert<PropertyValue<float>>(*intensity, error, false, false); if (convertedIntensity) { light.setIntensity(*convertedIntensity); } else { return nullopt; } } const auto intensityTransition = objectMember(value, "intensity-transition"); if (intensityTransition) { optional<TransitionOptions> transition = convert<TransitionOptions>(*intensityTransition, error); if (transition) { light.setIntensityTransition(*transition); } else { return nullopt; } } return { std::move(light) }; }