From: Matt Blair Date: Thu, 23 Jun 2016 23:57:24 +0000 (-0400) Subject: Adds initial framework for TangramView X-Git-Tag: submit/tizen_3.0/20161108.012559~48 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F09%2F80309%2F2;p=platform%2Fcore%2Flocation%2Fmaps-plugin-mapzen.git Adds initial framework for TangramView Change-Id: I648a6cff5f225f9b450abe40eeb6cab6b92ec085 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index bdc6ed3..676cb16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ SET(CMAKE_INSTALL_PREFIX /usr) SET(PREFIX ${CMAKE_INSTALL_PREFIX}) # Dependencies -SET(dependents "glib-2.0 gmodule-2.0 dlog libcurl capi-network-connection capi-maps-service") +SET(dependents "glib-2.0 gmodule-2.0 dlog libcurl capi-network-connection capi-maps-service evas ecore-evas") INCLUDE(FindPkgConfig) pkg_check_modules(pkgs REQUIRED ${dependents}) @@ -77,6 +77,7 @@ SET(SRCS src/mapzen/mapzen_jsonparser.cpp src/mapzen/mapzen_queue.c src/mapzen/mapzen_util.c + src/mapzen/tangram_view.cpp ) ADD_LIBRARY(${fw_name} SHARED ${SRCS}) @@ -88,7 +89,10 @@ SET_TARGET_PROPERTIES(${fw_name} CLEAN_DIRECT_OUTPUT 1 ) -TARGET_LINK_LIBRARIES(${fw_name} ${pkgs_LDFLAGS}) + +FIND_LIBRARY(TANGRAM_LIBRARY tangram lib/${ARCH}) + +TARGET_LINK_LIBRARIES(${fw_name} ${pkgs_LDFLAGS} ${TANGRAM_LIBRARY}) SET(PC_NAME ${fw_name}) SET(PC_DESCRIPTION "Tizen mapzen plugin Library") @@ -104,6 +108,7 @@ CONFIGURE_FILE( # Install INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${fw_name}.pc DESTINATION lib/pkgconfig) INSTALL(TARGETS ${fw_name} DESTINATION lib/maps/plugins) +INSTALL(FILES ${TANGRAM_LIBRARY} DESTINATION lib) #INCLUDE(FindPkgConfig) #pkg_check_modules(lib_pkgs REQUIRED diff --git a/packaging/maps-plugin-mapzen.spec b/packaging/maps-plugin-mapzen.spec index 65900b9..cba92f3 100644 --- a/packaging/maps-plugin-mapzen.spec +++ b/packaging/maps-plugin-mapzen.spec @@ -17,6 +17,8 @@ BuildRequires: pkgconfig(capi-network-connection) BuildRequires: pkgconfig(capi-maps-service) BuildRequires: capi-maps-service-plugin-devel BuildRequires: pkgconfig(json-glib-1.0) +BuildRequires: pkgconfig(ecore) +BuildRequires: pkgconfig(evas) Requires(post): /sbin/ldconfig Requires(postun): /sbin/ldconfig @@ -75,6 +77,7 @@ cp LICENSE %{buildroot}/usr/share/license/%{name} %manifest maps-plugin-mapzen.manifest %defattr(-,root,root,-) %{_prefix}/lib/maps/plugins/libmaps-plugin-mapzen.so* +%{_prefix}/lib/libtangram.so* %{_prefix}/lib/pkgconfig/maps-plugin-mapzen.pc /usr/share/license/maps-plugin-mapzen diff --git a/src/mapzen/tangram/ease.h b/src/mapzen/tangram/ease.h new file mode 100644 index 0000000..4e1f705 --- /dev/null +++ b/src/mapzen/tangram/ease.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace Tangram { + +using EaseCb = std::function; + +enum class EaseType : char { + linear = 0, + cubic, + quint, + sine, +}; + +template +T ease(T _start, T _end, float _t, EaseType _e) { + float f = _t; + switch (_e) { + case EaseType::cubic: f = (-2 * f + 3) * f * f; break; + case EaseType::quint: f = (6 * f * f - 15 * f + 10) * f * f * f; break; + case EaseType::sine: f = 0.5 - 0.5 * cos(M_PI * f); break; + default: break; + } + return _start + (_end - _start) * f; +} + +struct Ease { + float t; + float d; + EaseCb cb; + + Ease() : t(0), d(0), cb([](float) {}) {} + Ease(float _duration, EaseCb _cb) : t(-1), d(_duration), cb(_cb) {} + + bool finished() const { return t >= d; } + + void update(float _dt) { + t = t < 0 ? 0 : std::fmin(t + _dt, d); + cb(std::fmin(1, t / d)); + } +}; + +} diff --git a/src/mapzen/tangram/platform.h b/src/mapzen/tangram/platform.h new file mode 100644 index 0000000..9bb8589 --- /dev/null +++ b/src/mapzen/tangram/platform.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +/* Print a formatted message to the console + * + * Uses printf syntax to write a string to stderr (or logcat, on Android) + */ +void logMsg(const char* fmt, ...); + +/* Function type for a mapReady callback*/ +using MapReady = std::function; + +/* Request that a new frame be rendered by the windowing system + */ +void requestRender(); + +/* If called with 'true', the windowing system will re-draw frames continuously; + * otherwise new frames will only be drawn when 'requestRender' is called. + */ +void setContinuousRendering(bool _isContinuous); + +bool isContinuousRendering(); + +/* get system path of a font file */ +std::string systemFontPath(const std::string& _name, const std::string& _weight, const std::string& _face); + +/* Read a file as a string + * + * Opens the file at the _path and returns a string with its contents. + * If the file cannot be found or read, the returned string is empty. + */ +std::string stringFromFile(const char* _path); + +/* Read a file into memory + * + * Opens the file at _path then allocates and returns a pointer to memory + * containing the contents of the file. The size of the memory in bytes is written to _size. + * If the file cannot be read, nothing is allocated and nullptr is returned. + */ +unsigned char* bytesFromFile(const char* _path, size_t& _size); + +/* Function type for receiving data from a successful network request */ +using UrlCallback = std::function&&)>; + +/* Start retrieving data from a URL asynchronously + * + * When the request is finished, the callback @_callback will be + * run with the data that was retrieved from the URL @_url + */ +bool startUrlRequest(const std::string& _url, UrlCallback _callback); + +/* Stop retrieving data from a URL that was previously requested + */ +void cancelUrlRequest(const std::string& _url); + + +/* Set the priority of the current thread. Priority is equivalent + * to pthread niceness. + */ +void setCurrentThreadPriority(int priority); + +/* Get the font fallback ordered by importance, 0 being the first fallback + * (e.g. the fallback more willing resolve the glyph codepoint) + */ +std::string systemFontFallbackPath(int _importance, int _weightHint); + +void initGLExtensions(); diff --git a/src/mapzen/tangram/platform_tizen.h b/src/mapzen/tangram/platform_tizen.h new file mode 100644 index 0000000..b70d781 --- /dev/null +++ b/src/mapzen/tangram/platform_tizen.h @@ -0,0 +1,13 @@ +#pragma once + +#include "platform.h" +#include +#include + +bool shouldRender(); + +void setRenderCallbackFunction(std::function callback); + +void finishUrlRequests(); +void initUrlRequests(); +void setEvasGlAPI(Evas_GL_API* glApi); diff --git a/src/mapzen/tangram/properties.h b/src/mapzen/tangram/properties.h new file mode 100644 index 0000000..baac61e --- /dev/null +++ b/src/mapzen/tangram/properties.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +namespace Tangram { + +class Value; +struct PropertyItem; + +struct Properties { + using Item = PropertyItem; + + Properties(); + ~Properties(); + + Properties(const Properties& _other) = default; + Properties(Properties&& _other) = default; + Properties(std::vector&& _items); + Properties& operator=(const Properties& _other) = default; + Properties& operator=(Properties&& _other); + + const Value& get(const std::string& key) const; + + void sort(); + + void clear(); + + bool contains(const std::string& key) const; + + bool getNumber(const std::string& key, double& value) const; + + double getNumber(const std::string& key) const; + + bool getString(const std::string& key, std::string& value) const; + + const std::string& getString(const std::string& key) const; + + std::string asString(const Value& value) const; + + std::string getAsString(const std::string& key) const; + + const bool getAsString(const std::string& key, std::string& value) const; + + std::string toJson() const; + + void set(std::string key, std::string value); + void set(std::string key, double value); + + void setSorted(std::vector&& _items); + + // template void set(std::string key, Args&&... args) { + // props.emplace_back(std::move(key), Value{std::forward(args)...}); + // sort(); + // } + + const std::vector& items() const { return props; } + + int32_t sourceId; + + static bool keyComparator(const std::string& a, const std::string& b) { + if (a.size() == b.size()) { + return a < b; + } else { + return a.size() < b.size(); + } + } +private: + std::vector props; +}; + +} diff --git a/src/mapzen/tangram/tangram.h b/src/mapzen/tangram/tangram.h new file mode 100644 index 0000000..30d2d91 --- /dev/null +++ b/src/mapzen/tangram/tangram.h @@ -0,0 +1,176 @@ +#pragma once + +#include "properties.h" +#include "ease.h" +#include +#include +#include + +/* Tangram API + * + * Primary interface for controlling and managing the lifecycle of a Tangram + * map surface + */ + +namespace Tangram { + +class DataSource; + +// Create resources and initialize the map view using the scene file at the +// given resource path +void initialize(const char* _scenePath); + +// Load the scene at the given absolute file path asynchronously +void loadSceneAsync(const char* _scenePath, bool _useScenePosition = false, + std::function _platformCallback = {}, void *_cbData = nullptr); +// Load the scene at the given absolute file path synchronously +void loadScene(const char* _scenePath, bool _useScenePosition = false); + +// Request an update to the scene configuration; the path is a series of yaml keys +// separated by a '.' and the value is a string of yaml to replace the current value +// at the given path in the scene +void queueSceneUpdate(const char* _path, const char* _value); + +// Apply all previously requested scene updates +void applySceneUpdates(); + +// Initialize graphics resources; OpenGL context must be created prior to calling this +void setupGL(); + +// Resize the map view to a new width and height (in pixels) +void resize(int _newWidth, int _newHeight); + +// Update the map state with the time interval since the last update, returns +// true when the current view is completely loaded (all tiles are available and +// no animation in progress) +bool update(float _dt); + +// Render a new frame of the map view (if needed) +void render(); + +// Set the position of the map view in degrees longitude and latitude; if duration +// (in seconds) is provided, position eases to the set value over the duration; +// calling either version of the setter overrides all previous calls +void setPosition(double _lon, double _lat); +void setPosition(double _lon, double _lat, float _duration, EaseType _e = EaseType::quint); + +// Set the values of the arguments to the position of the map view in degrees +// longitude and latitude +void getPosition(double& _lon, double& _lat); + +// Set the fractional zoom level of the view; if duration (in seconds) is provided, +// zoom eases to the set value over the duration; calling either version of the setter +// overrides all previous calls +void setZoom(float _z); +void setZoom(float _z, float _duration, EaseType _e = EaseType::quint); + +// Get the fractional zoom level of the view +float getZoom(); + +// Set the counter-clockwise rotation of the view in radians; 0 corresponds to +// North pointing up; if duration (in seconds) is provided, rotation eases to the +// the set value over the duration; calling either version of the setter overrides +// all previous calls +void setRotation(float _radians); +void setRotation(float _radians, float _duration, EaseType _e = EaseType::quint); + +// Get the counter-clockwise rotation of the view in radians; 0 corresponds to +// North pointing up +float getRotation(); + +// Set the tilt angle of the view in radians; 0 corresponds to straight down; +// if duration (in seconds) is provided, tilt eases to the set value over the +// duration; calling either version of the setter overrides all previous calls +void setTilt(float _radians); +void setTilt(float _radians, float _duration, EaseType _e = EaseType::quint); + +// Get the tilt angle of the view in radians; 0 corresponds to straight down +float getTilt(); + +// Transform coordinates in screen space (x right, y down) into their longitude +// and latitude in the map view +void screenToWorldCoordinates(double& _x, double& _y); + +// Set the ratio of hardware pixels to logical pixels (defaults to 1.0) +void setPixelScale(float _pixelsPerPoint); + +// Set the camera type (0 = perspective, 1 = isometric, 2 = flat) +void setCameraType(int _type); + +// Get the camera type (0 = perspective, 1 = isometric, 2 = flat) +int getCameraType(); + +// Add a data source for adding drawable map data, which will be styled +// according to the scene file using the provided data source name; +void addDataSource(std::shared_ptr _source); + +// Remove a data source from the map; returns true if the source was found +// and removed, otherwise returns false. +bool removeDataSource(DataSource& _source); + +void clearDataSource(DataSource& _source, bool _data, bool _tiles); + +// Respond to a tap at the given screen coordinates (x right, y down) +void handleTapGesture(float _posX, float _posY); + +// Respond to a double tap at the given screen coordinates (x right, y down) +void handleDoubleTapGesture(float _posX, float _posY); + +// Respond to a drag with the given displacement in screen coordinates (x right, y down) +void handlePanGesture(float _startX, float _startY, float _endX, float _endY); + +// Respond to a fling from the given position with the given velocity in screen coordinates +void handleFlingGesture(float _posX, float _posY, float _velocityX, float _velocityY); + +// Respond to a pinch at the given position in screen coordinates with the given +// incremental scale +void handlePinchGesture(float _posX, float _posY, float _scale, float _velocity); + +// Respond to a rotation gesture with the given incremental rotation in radians +void handleRotateGesture(float _posX, float _posY, float _rotation); + +// Respond to a two-finger shove with the given distance in screen coordinates +void handleShoveGesture(float _distance); + +// Set whether the OpenGL state will be cached between subsequent frames; this improves rendering +// efficiency, but can cause errors if your application code makes OpenGL calls (false by default) +void useCachedGlState(bool _use); + +enum DebugFlags { + freeze_tiles = 0, // While on, the set of tiles currently being drawn will not update to match the view + proxy_colors, // Applies a color change to every other zoom level of tiles to visualize proxy tile behavior + tile_bounds, // Draws tile boundaries + tile_infos, // Debug tile infos + labels, // Debug label bounding boxes + tangram_infos, // Various text tangram debug info printed on the screen + all_labels, // Draw all labels + tangram_stats, // Tangram frame graph stats +}; + +// Set debug features on or off using a boolean (see debug.h) +void setDebugFlag(DebugFlags _flag, bool _on); + +// Get the boolean state of a debug feature (see debug.h) +bool getDebugFlag(DebugFlags _flag); + +// Toggle the boolean state of a debug feature (see debug.h) +void toggleDebugFlag(DebugFlags _flag); + +// Run this task on Tangram's main update loop. +void runOnMainLoop(std::function _task); + +// Run this task asynchronously to Tangram's main update loop. +void runAsyncTask(std::function _task); + +struct TouchItem { + std::shared_ptr properties; + float position[2]; + float distance; +}; + +const std::vector& pickFeaturesAt(float _x, float _y); + +float frameTime(); + +} + diff --git a/src/mapzen/tangram_view.cpp b/src/mapzen/tangram_view.cpp new file mode 100644 index 0000000..409b155 --- /dev/null +++ b/src/mapzen/tangram_view.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #include "tangram_view.hpp" + #include "tangram/tangram.h" + +TangramView::TangramView() +{ + // Nothing to do. +} + +TangramView::~TangramView() +{ + // Nothing to do. +} + +mapzen_error_e TangramView::create(maps_view_h view, maps_plugin_map_view_ready_cb callback) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::destroy(maps_view_h view) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::render(maps_view_h view, const maps_coordinates_h coord, double zoom, double angle) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::moveCenter(maps_view_h view, int delta_x, int delta_y) +{ + if (!view) { + return MAPZEN_ERROR_INVALID_PARAMETER; + } + + if (!isInitialized) { + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; + } + + if (delta_x == 0 && delta_y == 0) { + return MAPZEN_ERROR_NONE; + } + + // The delta_x and delta_y values are in pixels, so we need to determine the equivalent displacement in + // longitude and latitude to set the new center position. + + double x = 0.5 * w + (double)delta_x; + double y = 0.5 * h + (double)delta_y; // TODO: Check whether sign of delta_y needs to be flipped. + Tangram::screenToWorldCoordinates(x, y); + Tangram::setPosition(x, y); + + return MAPZEN_ERROR_NONE; +} + +mapzen_error_e TangramView::getCenter(maps_view_h view, maps_coordinates_h *center) +{ + if (!view || !center) { + return MAPZEN_ERROR_INVALID_PARAMETER; + } + + if (!isInitialized) { + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; + } + + double longitude = 0, latitude = 0; + Tangram::getPosition(longitude, latitude); + + if (*center == nullptr) { + maps_coordinates_create(latitude, longitude, center); + } else { + maps_coordinates_set_latitude(*center, latitude); + maps_coordinates_set_longitude(*center, longitude); + } + + return MAPZEN_ERROR_NONE; +} + +mapzen_error_e TangramView::setScalebarEnabled(maps_view_h view, bool enable) +{ + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::getScalebarEnabled(maps_view_h view, bool *enabled) +{ + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::convertScreenToGeolocation(maps_view_h view, int x, int y, maps_coordinates_h *coord) +{ + if (!view || !coord) { + return MAPZEN_ERROR_INVALID_PARAMETER; + } + + if (!isInitialized) { + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; + } + + double longitude = (double)x, latitude = (double)y; + Tangram::screenToWorldCoordinates(longitude, latitude); + + if (*coord == nullptr) { + maps_coordinates_create(latitude, longitude, coord); + } else { + maps_coordinates_set_latitude(*coord, latitude); + maps_coordinates_set_longitude(*coord, longitude); + } + + return MAPZEN_ERROR_NONE; +} + +mapzen_error_e TangramView::convertGeolocationToScreen(maps_view_h view, const maps_coordinates_h coord, int *x, int *y) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::getMinZoomLevel(maps_view_h view, int *zoom) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::getMaxZoomLevel(maps_view_h view, int *zoom) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::onViewObject(maps_view_h view, const maps_view_object_h object, maps_view_object_operation_e operation) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::initOpenGL() +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::initOpenGLSurface(maps_view_h view) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +mapzen_error_e TangramView::initMap(maps_view_h view, maps_plugin_map_view_ready_cb callback) +{ + // TODO + return MAPZEN_ERROR_SERVICE_NOT_AVAILABLE; +} + +void TangramView::setMapType(maps_view_h view) +{ + // TODO +} + +Eina_Bool TangramView::idlerCb(void *data) +{ + // TODO + return false; +} + +void TangramView::readyMapCb(maps_view_h view) +{ + // TODO +} + +void TangramView::renderingCb(void *data) +{ + // TODO +} + +void TangramView::pixelGetCb(void *data, Evas_Object *obj) +{ + // TODO +} + +void TangramView::processViewObject(maps_view_h view, const maps_view_object_h object, maps_view_object_operation_e operation) +{ + // TODO +} diff --git a/src/mapzen/tangram_view.hpp b/src/mapzen/tangram_view.hpp new file mode 100644 index 0000000..72d5098 --- /dev/null +++ b/src/mapzen/tangram_view.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "mapzen_types.h" + +class TangramView { + +public: + TangramView(); + ~TangramView(); + + mapzen_error_e create(maps_view_h view, maps_plugin_map_view_ready_cb callback); + mapzen_error_e destroy(maps_view_h view); + mapzen_error_e render(maps_view_h view, const maps_coordinates_h coord, double zoom, double angle); + mapzen_error_e moveCenter(maps_view_h view, int delta_x, int delta_y); + mapzen_error_e getCenter(maps_view_h view, maps_coordinates_h *center); + mapzen_error_e setScalebarEnabled(maps_view_h view, bool enable); + mapzen_error_e getScalebarEnabled(maps_view_h view, bool *enabled); + mapzen_error_e convertScreenToGeolocation(maps_view_h view, int x, int y, maps_coordinates_h *coord); + mapzen_error_e convertGeolocationToScreen(maps_view_h view, const maps_coordinates_h coord, int *x, int *y); + mapzen_error_e getMinZoomLevel(maps_view_h view, int *zoom); + mapzen_error_e getMaxZoomLevel(maps_view_h view, int *zoom); + mapzen_error_e onViewObject(maps_view_h view, const maps_view_object_h object, maps_view_object_operation_e operation); + +private: + mapzen_error_e initOpenGL(); + mapzen_error_e initOpenGLSurface(maps_view_h view); + mapzen_error_e initMap(maps_view_h view, maps_plugin_map_view_ready_cb callback); + void setMapType(maps_view_h view); + static Eina_Bool idlerCb(void *data); + static void readyMapCb(maps_view_h view); + static void renderingCb(void *data); + static void pixelGetCb(void *data, Evas_Object *obj); + static void processViewObject(maps_view_h view, const maps_view_object_h object, maps_view_object_operation_e operation); + +private: + + Evas_Object *image = nullptr; + Evas_GL_Context *context = nullptr; + Evas_GL_Surface *surface = nullptr; + Evas_GL_Config *config = nullptr; + Evas_GL *gl = nullptr; + Evas_GL_API *api = nullptr; + + bool isInitialized = false; + + int x = 0, y = 0, w = 0, h = 0; + double lat = 0., lng = 0., zoom = 0., angle = 0.; + + Ecore_Idler *idler = nullptr; + bool redraw = false; + + maps_plugin_map_view_ready_cb readyCb = nullptr; +};