{"id":38110392,"url":"https://github.com/carrot-ar/carrot","last_synced_at":"2026-01-16T21:56:40.641Z","repository":{"id":57555742,"uuid":"104118133","full_name":"carrot-ar/carrot","owner":"carrot-ar","description":"🥕 Build multi-device AR applications","archived":false,"fork":false,"pushed_at":"2018-02-14T15:33:09.000Z","size":278,"stargazers_count":47,"open_issues_count":6,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-10T03:23:26.511Z","etag":null,"topics":["augmented-reality","framework","golang","gorilla","gorilla-websocket","mixed-reality","networking","websockets"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/carrot-ar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-19T19:20:42.000Z","updated_at":"2024-11-23T06:43:10.000Z","dependencies_parsed_at":"2022-09-14T10:51:28.245Z","dependency_job_id":null,"html_url":"https://github.com/carrot-ar/carrot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/carrot-ar/carrot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carrot-ar%2Fcarrot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carrot-ar%2Fcarrot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carrot-ar%2Fcarrot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carrot-ar%2Fcarrot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/carrot-ar","download_url":"https://codeload.github.com/carrot-ar/carrot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carrot-ar%2Fcarrot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28484345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["augmented-reality","framework","golang","gorilla","gorilla-websocket","mixed-reality","networking","websockets"],"created_at":"2026-01-16T21:56:40.010Z","updated_at":"2026-01-16T21:56:40.630Z","avatar_url":"https://github.com/carrot-ar.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/carrot-ar/carrot-ios/wiki/resources/Carrot@2x.png\" alt=\"Carrot\" width=\"300\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://travis-ci.org/carrot-ar/carrot\"\u003e\u003cimg src=\"https://travis-ci.org/carrot-ar/carrot.svg?branch=master\" alt=\"build status\"\u003e\u003c/a\u003e\n\u003ca href=\"\"\u003e\u003cimg src=\"https://codecov.io/gh/carrot-ar/carrot/branch/master/graph/badge.svg\" alt=\"code coverage\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nCarrot is an easy-to-use, real-time framework for building applications with multi-device AR capabilities. It works using WebSockets, Golang, client libraries written for iOS, and a unique location tracking system based on iBeacons that we aptly named The Picnic Protocol. Using Carrot, multi-device AR apps can be created with high accuracy location tracking to provide rich and lifelike experiences. To see for yourself, check out Scribbles, a multiplayer drawing application made with Carrot. You can see a demo video [here](https://www.youtube.com/watch?v=6EVtb0pJPgk) and the code [here](https://github.com/carrot-ar/scribbles).\n\nTo see documentation for the iOS Client library visit the [README for carrot-ios](https://github.com/carrot-ar/carrot-ios/blob/master/README.md)\n\n|    | 🗂 Table of Contents |\n|:--:|----------------------\n| ✨ | [Features](#features)\n| 📋 | [To-Do](#to-do)\n| ⚙️ | [Design](#design)\n| 🛠 | [Building an Application with Carrot](#building-an-application-with-carrot)\n| 🥗 | [The Picnic Protocol](#the-picnic-protocol)\n| ✉️ | [Message Format](#message-format)\n| 🎙 | [Sending Messages to Carrot](#sending-messages-to-carrot)\n| 📨 | [Receiving Messages from Carrot](#receiving-messages-from-carrot)\n| 📺 | [Broadcasting Responses](#broadcasting-responses)\n| 🌎 | [Sessions](#sessions)\n\n## Features\n- Rapid development of multi-device AR applications with little Go or server knowledge\n- WebSocket connection and state management\n- High accuracy AR location tracking with the [Picnic Protocol](https://github.com/carrot-ar/carrot-ios#-the-picnic-protocol)\n- Sessions and session management\n- Middleware \n- Extensible controllers\n- Custom endpoints \n- Performance optimizations\n- High throughput (30k messages/second tested on 4 CPU test machine)\n- ~100 microsecond average to service one request \n\n## To-Do\n- Support for external session management using Redis, memcached, etc\n- Bug fixes for picnic protocol implementation \n- Object Relational Mapping library to have a true Model-Controller design\n- [Universal Scene Description](https://github.com/PixarAnimationStudios/USD) support \n\n## Design\n\nBellow is a high level design of the Carrot framework. More detailed diagrams will be provided when time permits.\n\n\n\u003cimg src=\"https://i.imgur.com/sHkF7Hl.png\" alt=\"carrot flow\"\u003e\n\n\n## Building an application with Carrot\n\nBuilding applications with Carrot is incredibly simple. Check out this echo application that echos a payload from one device into the AR space of all connected devices: \n\n``` go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/carrot-ar/carrot\"\n)\n\n// Controller declaration\ntype EchoController struct{}\n\n//Controller method implementation\nfunc (c *EchoController) Echo(req *carrot.Request, br *carrot.Broadcast) {\n\tmessage, err := carrot.CreateDefaultResponse(req)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tbr.Broadcast(message)\n}\n\nfunc main() {\n\n\t// Register endpoint by providing endpoint, controller, and method, and if endpoint requires streaming\n\tcarrot.Add(\"echo\", EchoController{}, \"Echo\", true)\n\n\t// Run the server to handle traffic\n\tcarrot.Run()\n}\n```\nThe example above ommits extra functionality to showcase the basic components required to connect carrot to your application. The required components are the following:\n\n* Import inclusion\n* Controller declaration\n* Controller method(s) implementation\n* Main method that\n\t* Registers connections between methods and controllers for Carrot to route and maintain\n\t* Runs the Carrot server\n\t\nController methods receieve requests and broadcast responses to clients. Requests can be passed as-is, demonstrated with the CreateDefaultResponse method above, or information can be appended before responses are broadcasted.\n\nTo make the framework interact with platform-specific code, developers will need to implement the Carrot client framework. Currently, only iOS support exists. To see how to do so, visit the carrot-ios repository [https://github.com/carrot-ar/carrot-ios](https://github.com/carrot-ar/carrot-ios)\n\n## The Picnic Protocol\n\nThe Picnic Protocol (patent pending) is a set of rules and standards that provide a way for devices to communicate local AR events as well as understand foreign ones. More specifically, however, it relies on both decentralized and centralized network topologies in order to solve the problem of understanding events that happen in foreign coordinate spaces.\nThe protocol's \"handshake\" begins by designating the first device to join the session as the primary device. The primary device has two responsibilities:\n\n- It must provide other devices a way to know that they are immediately next to it in physical space, which we'll refer to as the \"immediate ping\". On iOS, this is achieved by broadcasting iBeacon signals from the primary device.\n\n- It must let the server know what it's current position in physical space is whenever the server asks for it, which the server does by sending a message with a reserved endpoint. At the moment of the immediate ping, the server asks the primary device for its position in physical space. We'll refer to this as TP, or the primary device's transform.\n\nThe rest of the devices in a session are referred to as secondary devices. Secondary devices must be able to listen for the immediate ping from the primary device and let the server know that they received this immediate ping by sending it their own position in physical space at that moment in time. We refer to this as TL, or the local transform. \nThe state of the environment between a secondary device and the primary device at the moment of the immediate ping is illustrated below.\n\n![figure 1](https://i.imgur.com/yTr9OEg.png)\n*The first step of the invention’s handshake, shown from the perspective of the secondary device. TL is the vector reflecting where the secondary device travelled to receive the immediate ping from the primary device. TP reflects where the primary device travelled to send the immediate ping to the secondary device.*\n\nAfter receiving the immediate ping, a secondary device is considered to be authenticated and ready to interact with other devices in the session. The invention uses the TL and TP relationship between every secondary device and the primary device in order to calculate the primary device’s origin in the secondary device’s coordinate space. This equation, explained in the image below, acts as the bridge between a secondary device and any other authenticated device in the network, whether that be the primary device or another secondary device.\n\nThis is the core of protocol. Clients are responsible for being able to send and receive the immediate ping and the server is responsible for maintaining the TL and TP relationship for every authenticated device in the network, as well as converting the locations in messages themselves before broadcasting them to clients.\n\n![figure 2](https://i.imgur.com/IEsfau0.png)\n*The calculation of OP, which is the vector resulting from the difference of TL and TP. Visually speaking, OP can be calculated by “walking along” TL and then walking in the opposite direction of TP. Being able to derive OP via this relationship allows the server to convert a coordinate that originated in the coordinate system of a secondary device to one that is now relative to the origin of the primary device. This equation can be applied a second time over in order to do secondary device to secondary device conversions.*\n\nThe final step in picnic’s coordinate conversion work is to take the coordinates of a local event, referred to as EL, and convert it to the primary device’s coordinate space. This results in a new vector, EP, which the server populates the outgoing message with before broadcasting it to the primary device. The calculation of EP is illustrated below.\n\nThe protocol is a platform-agnostic way of performing coordinate conversion. It was designed specifically for multi-device augmented reality on mobile devices though, and is well tailored for that use case. The only thing it requires devices to have, however,  is the ability to connect to a network. Although Bluetooth and iBeacon technologies were chosen as the way to do inter-device communication in the iOS framework, one can imagine this happening over something like a P2P network instead, for example.\n\n![figure 3](https://i.imgur.com/uRsqDEH.png)\n*The calculation of EP, which is EL converted to be relative to the primary device’s origin. The server mutates the message sent by the secondary device who rendered the event at EL, effectively replacing EL with EP. This allows the primary device to take the incoming message and render it as is, without having to even consider where the coordinate originated from. It allows the primary device to treat all incoming messages as if they originated locally.*\n\n\n## Message Format\n\nCarrot has two message types: request and responses. These are represented by the []byte type.\n\nRequests are created and sent by the client framework to the server framework. Conversely, responses are created and sent by a developer defined controller back to the client framework. The end of a request's path marks the beginning of the corresponding response's path. \n\nThe structure of messages are identical, so the two types of messages represent the opposite directions (and ultimate paths) data travels. Requests and responses are in the form of the following JSON:\n\n\t{\n\t\t\"session_token\": \"E621E1F8-C36C-495A-93FC-0C247A3E6E5F\",\n\t\t\"endpoint\": \"echo\",\n\t\t\"payload\": {\n\t\t\t\"offset\": {\n\t\t\t\t\"x\": 3.2,\n\t\t\t\t\"y\": 1.3,\n\t\t\t\t\"z\": 4.0\n\t\t\t},\n\t\t\t\"params\": {\n\t\t\t\t\"foo\": \"bar\"\n\t\t\t}\n\t\t}\n\t}\n\n## Sending Messages to Carrot\n\nCarrot expects to recieve requests from clients.\n\nIn order for a client to send messages to other devices, a route must be defined so the server knows the destination controller and method to handle the incoming requests. Consider this example application snippet:\n\n``` go\n// Controller declaration\ntype ExampleController struct{}\n\n//Controller method implementation\nfunc (c *ExampleController) PrintFooParameterToConsole(req *carrot.Request, br *carrot.Broadcast) {\n\tfmt.Println(req.Params[\"foo\"])\n}\n\nfunc main() {\n  \t// Register endpoint by providing endpoint, controller, and method, and if endpoint requires streaming\n\tcarrot.Add(\"print_foo_param\", ExampleController{}, \"PrintFooParameterToConsole\", true)\n}\n```\n\nIn order to send a message to this endpoint, all we need to do is specify the endpoint in our request. A request to be sent to the endpoint defined above could look something like this :\n\n```\n{\n\t\"session_token\": \"E621E1F8-C36C-495A-93FC-0C247A3E6E5F\",\n\t\"endpoint\": \"print_foo_param\",\n\t\"payload\": {\n\t\t\"offset\": {\n\t\t\t\"x\": 3,\n\t\t\t\"y\": 1,\n\t\t\t\"z\": 4\n\t\t},\n\t\t\"params\": {\n\t\t\t\"foo\": \"bar\"\n\t\t}\n\t}\n}\n```\n\nIf this request is sent to the server over the WebSocket connection established, it will create a new instance of the `ExampleController` and route the message to the `PrintFooParameterToConsole` function. Inside the function, we will log the value of the `foo` parameter to the server log. \n\nOnce the request reached its intended controller method, it has reached the end of its life cycle.\n\n## Receiving Messages from Carrot\n\nClients can expect to receive responses from Carrot. Assuming the request is not explicitly modified in the developer-defined controller, then the response should be exactly the same as the request. \n\nIn the case that the developer wants to modify requests before the devices are broadcasted and recieve these responses, custom responses can be built.\n\n### Responses\n\nThere are two different types of responses: default and custom.\n\n#### Default Responses\n\nIf the developer wants to forward requests as they are, then they can use the create a default response. The following snippet shows how to create a default response:\n\n``` go\n\nfunc (c *ExampleController) CreateDefaultResponse(req *carrot.Request, br *carrot.Broadcast) {\n\tdefaultResponse, err := carrot.CreateDefaultResponse(req)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n}\n\n```\n\nAs you can see above, the contents of the request received by the controller are dumped into the generating the contents of the new response. Behind the scenes, the JSON representing the message stucture is created and returned as a []byte ready to be broadcasted. Once created, a default reponse cannot be modified. This option is therefore used for sake of brevity in relevant use cases.\n\n#### Custom Responses\n\nIf the developer wants to add extra information to the message, then a custom response must be created.  Like the default response, the contents are the request are placed in the response. However, the custom response requires the developer to explictly call more functions to create it. \n\nThe first two functions define the contents for all of the fields that are copied from the request. \n\n``` go \n\nfunc (c *EchoController) EchoExtendable(req *carrot.Request, br *carrot.Broadcast) {\n\ttoken := string(req.SessionToken)\n\tpayload, err := carrot.NewPayload(token, req.Offset, req.Params)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tres, err := carrot.NewResponse(token, \"Echo\", payload)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\t...\n}\n\n```\n\nThe next and most interesting functions to note are the `AddParam` and `AddParams` functions. They allow the developer to append key-value pairs to a custom response.\n\n``` go\n\nfunc (c *EchoController) EchoExtendable(req *carrot.Request, br *carrot.Broadcast) {\n\t...\n\toneFishTwoFish := ResponseParams{\"red\": \"fish\", \"blue\": \"fish\"}\n\tres.AddParam(\"someKey\", \"someValue\")\n\tres.AddParams(oneFishTwoFish)\n\t...\n}\n\t\n```\n\nFinally, a custom response must be packaged into a JSON object. \n\n``` go\n\nfunc (c *EchoController) EchoExtendable(req *carrot.Request, br *carrot.Broadcast) {\n\t...\n\tmessage, err := res.Build()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\t...\n}\n\t\n```\n\nThe resulting message is ready to be broadcasted to clients and can no longer be modified.\n\n## Broadcasting Responses\n\nThe broadcast module, available in all controller implementations, has a few options for narrowing down which clients to send a message to. Since all clients have a session associated with them, there is a 1-to-1 relationship between sessions and clients. Thus, every client has a `SessionToken` which is accessible within the client and within the session store internal to Carrot. \n\n#### Broadcasting to all clients\n\n``` go\ncarrot.Broadcast(/* carrot response  */)\n```\n\n#### Broadcasting to a subset of clients\n\n``` go\ncarrot.Broadcast(/* carrot response */, sessionToken1, sessiontoken2)\n```\nor\n``` go\nrecipients := []string{sessionToken1, sessionToken2, sessionToken3)\ncarrot.Broadcast(/* carrot response */, recipients)\n```\n\nOne way to make the best use of this feature is to keep sessions associated with users in a datastore connected to carrot. Then, a simple query can return a set of sessions that should be sent a response. Here is some pseudocode demonstrating this:\n``` go\nfunc (c *ExampleController) SendHelloToAll(r *carrot.Request, b *carrot.Broadcast) {\n\t/* build up a response here */\n\t/* database call to get a list of session tokens based on a query */\n\tb.Broadcast(/* response */, /* array with session tokens */)\n}\n```\n\nOnce the response reaches its intended recipient(s), it has reached the end of its life cycle.\n\n## Sessions\n\nMaintaining the state of clients are done using sessions inside of Carrot. Due to the shared, concurrent access of sessions throughout the lifecycle of a request, they are stored isnide of Golang's `sync.Map`. An interface is provided such that future extensibility could be easily integrated into Carrot to allow session storage within in-memory data stores like Redis. \n\nWithin Carrot, the session store is maintained using a singleton pattern and within any point of carrot the current state of sessions is accessible by calling the `NewDefaultSessionManager()`, which returns a pointer to the `SessionStore` interface. See the GoDoc for the `SessionStore` interface and `DefaultSessionStore` struct for more details.\n\n### Resuming a Session\n\nTo be implemented\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarrot-ar%2Fcarrot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcarrot-ar%2Fcarrot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarrot-ar%2Fcarrot/lists"}