Git Product home page Git Product logo

nakama-defold's Introduction

Nakama Defold/Lua client

Lua client for Nakama server written in Lua 5.1.

Nakama is an open-source server designed to power modern games and apps. Features include user accounts, chat, social, matchmaker, realtime multiplayer, and much more.

This client implements the full API and socket options with the server. It's written in Lua 5.1 to be compatible with Lua based game engines.

Full documentation is available here.

Getting Started

You'll need to setup the server and database before you can connect with the client. The simplest way is to use Docker but have a look at the server documentation for other options.

  1. Install and run the servers. Follow these instructions.

  2. Add the client to your project.

  • In Defold projects you need to add the URL of a stable release or the latest development version as a library dependency to game.project. The client will now show up in nakama folder in your project.
  1. Add dependencies to your project. In Defold projects you need to add one of the following dependencies to game.project:

  2. Use the connection credentials to initialise the nakama client.

    local defold = require "nakama.engine.defold"
    local nakama = require "nakama.nakama"
    local config = {
        host = "127.0.0.1",
        port = 7350,
        use_ssl = false,
        username = "defaultkey",
        password = "",
        engine = defold,
        timeout = 10, -- connection timeout in seconds
    }
    local client = nakama.create_client(config)
  3. (Optional) Nakama uses base64 decoding for session the session tokens and both base64 encoding and decoding of match data. The default base64 encoder and decoder is written in Lua. To increase performance of the base64 encode and decode steps it is possible to use a base64 encoder written in C. In Defold projects you need to add the following dependency to game.project:

Usage

The client has many methods to execute various features in the server or open realtime socket connections with the server.

Authenticate

There's a variety of ways to authenticate with the server. Authentication can create a user if they don't already exist with those credentials. It's also easy to authenticate with a social profile from Google Play Games, Facebook, Game Center, etc.

local client = nakama.create_client(config)

local email = "[email protected]"
local password = "batsignal"
local session = client.authenticate_email(email, password)
pprint(session)

Note: see Requests section below for running this snippet (a)synchronously.

Sessions

When authenticated the server responds with an auth token (JWT) which can be used to authenticate API requests. The token contains useful properties and gets deserialized into a session table.

local client = nakama.create_client(config)

local session = client.authenticate_email(email, password)

print(session.created)
print(session.token) -- raw JWT token
print(session.expires)
print(session.user_id)
print(session.username)
print(session.refresh_token) -- raw JWT token for use when refreshing the session
print(session.refresh_token_expires)
print(session.refresh_token_user_id)
print(session.refresh_token_username)

-- Use the token to authenticate future API requests
nakama.set_bearer_token(client, session.token)

-- Use the refresh token to refresh the authentication token
nakama.session_refresh(client, session.refresh_token)

It is recommended to store the auth token from the session and check at startup if it has expired. If the token has expired you must reauthenticate. If the token is about to expire it has to be refreshed. The expiry time of the token can be changed as a setting in the server. You can store the session using session.store(session) and later restored it using session.restore():

local nakama_session = require "nakama.session"

local client = nakama.create_client(config)

-- restore a session
local session = nakama_session.restore()

if session and nakama_session.is_token_expired_soon(session) and not nakama.is_refresh_token_expired(session) then
    print("Session has expired or is about to expire. Refreshing.")
    session = nakama.session_refresh(client, session.refresh_token)
    nakama_session.store(session)
elseif not session or nakama_session.is_refresh_token_expired(session) then
    print("Session does not exist or it has expired. Must reauthenticate.")
    session = client.authenticate_email("[email protected]", "foobar123", nil, true, "britzl")
    nakama_session.store(session)
end
client.set_bearer_token(session.token)

Requests

The client includes lots of built-in APIs for various features of the game server. These can be accessed with the methods which either use a callback function to return a result (ie. asynchronous) or yield until a result is received (ie. synchronous and must be run within a Lua coroutine).

local client = nakama.create_client(config)

-- using a callback
client.get_account(function(account)
    print(account.user.id);
    print(account.user.username);
    print(account.wallet);
end)

-- if run from within a coroutine
local account = client.get_account()
print(account.user.id);
print(account.user.username);
print(account.wallet);

The Nakama client provides a convenience function for creating and starting a coroutine to run multiple requests synchronously one after the other:

nakama.sync(function()
    local account = client.get_account()
    local result = client.update_account(request)
end)

Retries

Nakama has a global and per-request retry configuration to control how failed API calls are retried.

    local retries = require "nakama.util.retries"

    -- use a global retry policy with 5 attempts with 1 second intervals
    local config = {
        host = "127.0.0.1",
        port = 7350,
        username = "defaultkey",
        password = "",
        retry_policy = retries.fixed(5, 1),
        engine = defold,
    }
    local client = nakama.create_client(config)

    -- use a retry policy specifically for this request
    -- 5 retries at intervals increasing by 1 second between attempts (eg 1s, 2s, 3s, 4s, 5s)
    nakama.list_friends(client, 10, 0, "", retries.incremental(5, 1))

Cancelling requests

Create a cancellation token and pass that with a request to cancel the request before it has completed.

    -- use a global retry policy with 5 attempts with 1 second intervals
    local config = {
        host = "127.0.0.1",
        port = 7350,
        username = "defaultkey",
        password = "",
        retry_policy = retries.fixed(5, 1),
        engine = defold,
    }
    local client = nakama.create_client(config)

    -- create a cancellation token
    local token = nakama.cancellation_token()

    -- start a request and proivide the cancellation token
    nakama.list_friends(client, 10, 0, "", nil, callback, token)

    -- immediately cancel the request without waiting for the request callback to be invoked
    nakama.cancel(token)

Socket

You can connect to the server over a realtime WebSocket connection to send and receive chat messages, get notifications, and matchmake into a multiplayer match.

You first need to create a realtime socket to the server:

local client = nakama.create_client(config)

-- create socket
local socket = client.create_socket()

nakama.sync(function()
    -- connect
    local ok, err = socket.connect()
end)

Then proceed to join a chat channel and send a message:

-- send channel join message
local channel_id = "pineapple-pizza-lovers-room"
local result = socket.channel_join(socket, 1, channel_id, false, false)

-- send channel messages
local result = socket.channel_message_send(channel_id, "Pineapple doesn't belong on a pizza!")

Handle events

A client socket has event listeners which are called on various events received from the server. Example:

socket.on_disconnect(function(message)
    print("Disconnected!")
end)

Available listeners:

  • on_disconnect - Handles an event for when the client is disconnected from the server.
  • on_channel_presence_event
  • on_match_presence_event
  • on_match_data
  • on_match
  • on_matchmaker_matched
  • on_notifications
  • on_party_presence_event
  • on_party
  • on_party_data
  • on_party_join_request
  • on_status_presence_event
  • on_status
  • on_stream_data
  • on_error
  • on_channel_message
  • on_channel_message

Match data

Nakama supports any binary content in data attribute of a match message. Regardless of your data type, the server only accepts base64-encoded data, so make sure you don't post plain-text data or even JSON, or Nakama server will claim the data malformed and disconnect your client (set server logging to debug to detect these events).

Nakama will automatically base64 encode your match data if the message was created using nakama.create_match_data_message(). Nakama will also automatically base64 decode any received match data before calling the on_matchdata listener.

local json = require "nakama.util.json"

local match_id = "..."
local op_code = 1
local data = json.encode({
    dest_x = 1.0,
    dest_y = 0.1,
})

-- send a match data message. The data will be automatically base64 encoded.
socket.match_data(match_id, op_code, data)

In a relayed multiplayer, you'll be receiving other clients' messages. The client has already base64 decoded the message data before sending it to the on_matchdata listener. If the data was JSON encoded, like in the example above, you need to decode it yourself:

socket.on_matchdata(function(message)
    local match_data = message.match_data
    local data = json.decode(match_data.data)
    pprint(data)                            -- gameplay coordinates from the example above
end)

Messages initiated by the server in an authoritative match will come as valid JSON by default.

Adapting to other engines

Adapting the Nakama Defold client to another Lua based engine should be as easy as providing another engine module when configuring the Nakama client:

local myengine = require "nakama.engine.myengine"
local nakama = require "nakama.nakama"
local config = {
    engine = myengine,
}
local client = nakama.create_client(config)

The engine module must provide the following functions:

  • http(config, url_path, query_params, method, post_data, cancellation_token, callback) - Make HTTP request.

    • config - Config table passed to nakama.create()
    • url_path - Path to append to the base uri
    • query_params - Key-value pairs to use as URL query parameters
    • method - "GET", "POST"
    • post_data - Data to post
    • cancellation_token - Check if cancellation_token.cancelled is true
    • callback - Function to call with result (response)
  • socket_create(config, on_message) - Create socket. Must return socket instance (table with engine specific socket state).

    • config - Config table passed to nakama.create()
    • on_message - Function to call when a message is sent from the server
  • socket_connect(socket, callback) - Connect socket.

    • socket - Socket instance returned from socket_create()
    • callback - Function to call with result (ok, err)
  • socket_send(socket, message, callback) - Send message on socket.

    • socket - Socket instance returned from socket_create()
    • message - Message to send
    • callback - Function to call with message returned as a response (message)
  • uuid() - Create a UUID

API codegen

Refer to instructions in codegen.

Generate Docs

API docs are generated with Ldoc and deployed to GitHub pages.

When changing the API comments, rerun Ldoc and commit the changes in docs/*.

Note: Comments for nakama/nakama.lua must be made in codegen/main.go.

To run Ldoc:

# in the project root, generate nakama.lua
# requires go and https://github.com/heroiclabs/nakama to be checked out
go run codegen/main.go -output nakama/nakama.lua ../nakama/apigrpc/apigrpc.swagger.json

# install ldoc (mac)
brew install luarocks
luarocks install ldoc

# run ldoc
doc . -d docs

Unit tests

Unit tests can be found in the tests folder. Run them using Telescope (fork which supports Lua 5.3+):

./tsc -f test/test_client.lua test/test_socket.lua test/test_session.lua

Contribute

The development roadmap is managed as GitHub issues and pull requests are welcome. If you're interested to enhance the code please open an issue to discuss the changes or drop in and discuss it in the community forum.

License

This project is licensed under the Apache-2 License.

nakama-defold's People

Contributors

agulev avatar britzl avatar docsncode avatar gpene avatar mofirouz avatar ronkip avatar samsthegreatest avatar tomglenn avatar unclenight avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nakama-defold's Issues

Include uri.lua license

nakama/util/uri.lua includes a link to the original source blob, but doesn't mention or include the project's MIT license.

The license should be included, perhaps adding that module to it's own folder, renaming it to init.lua and including their LICENSE.md file.

cc @britzl

3.0.0: nakama/socket.lua:18: attempt to call global 'log' (a nil value)

Reproduce by receiving a socket event that does not have a bound listener.

ERROR:SCRIPT: nakama/socket.lua:18: attempt to call global 'log' (a nil value)
stack traceback:
  nakama/socket.lua:18: in function on_message
  nakama/engine/defold.lua:122: in function on_message
  nakama/engine/defold.lua:164: in function <nakama/engine/defold.lua:152>

"Unable to create a timer, the lua context does not have a timer world"

I'm in the process of making a test project to get to grips with Nakama in Defold.

When using nakama.create_socket() I get the following error:

ERROR:SCRIPT: Unable to create a timer, the lua context does not have a timer world

I believe this is ultimately triggered by: https://github.com/heroiclabs/nakama-defold/blob/master/nakama/engine/defold.lua#L95

There is no reference to timer in nakama.create_socket(), but the function does call create_socket() in the Defold engine module.

on_match_data does not receive opcode

reposting this issue that I reported on the forum.

I get all the data from server, I just do not receive the opcode.

socket.on_match_data(Constants.SOCKET_CONNECTION, function(message)
	print('opcode', message.match_data.op_code) ---> nil
	print('match_data', json.encode(message.match_data))
end
dispatcher.broadcast_message(opcode, nk.json_encode(data), nil, message.sender)

nothing too much crazy, since I can pass the opcode to the data payload, just reporting because it's how it is on the docs.

Bob build failure: uuid.lua

Hi, stumbled upon the curious issue where the latest bob can't build the project while the Editor builds are fine:

The build failed for the following reasons:
ERROR nakama/util/uuid.lua:0 the module '/math.lua' can't be found

According to this forum post, Defold runtime exports most of the std library modules so requiring them is not necessary.

Fixed it in my forked repo by editing the UUID module (removed require() statements), but decided to create an issue before submitting a PR to discuss whether it's the right thing to do. I'm aware that BOB and Defold Editor builds work differently under the hood and it feels like this should be addressed in bob repo in the first place. Since the Editor builds are okay with requiring a stdlib modules, maybe improving bob is what should be done instead.

What do you suggest?

Thanks for reading another text sheet ๐Ÿ˜†

RPC Calls not working

After referring to a number of examples and documents here:
https://heroiclabs.com/docs/runtime-code-basics/#rpc-example
https://heroiclabs.com/docs/gameplay-multiplayer-server-multiplayer/#manually
And api references, I was going a little crazy trying to determine why my payload was always and empty string.
I havent tracked it in the code (nor am I going to spend time doing so right now) but there is no need to use it.
All the payload and information in lua modules can be extracted from the first param - context.
Heres a quick example for people having problems getting rpc to work:

local function my_test_rpc(context)

    local payload = context.query_params.payload
    local data = nk.json_decode(payload[1])
    print("data.name = ", data.name)
end

nk.register_rpc(my_test_rpc, "test_rpc")

To call it from the client I use something like this:

local json = require "nakama.util.json"
local myobj = { name = "David" }
local jsonbody = json.encode(myobj)
nakama.rpc_func2( self.nk_client, "test_rpc", jsonbody, nil, function(result)
    print("done")
end)

If you have any problems please let me know. The above is untested, but its very close to what Im using.

Missing Party API

I think the party API is missing from the interface.
https://heroiclabs.com/docs/social-realtime-parties/#join-party

I have tried to manually add it, but Im not sure its working properly (dont know if there needs to be an extra module on the server).
I'm running the docker-compose nakama with version 3.3.0 in it. Server is Ubuntu 16.04 - xenial (this may be an issue - will be moving to 20 soon)
https://github.com/heroiclabs/nakama

I have tried extending the interfaces like so:

--------------------------------------------------------------------------------
--- Create a match_create socket message
function M.create_party_create_message( open, maxsize )
	local message = {
		party_create = {
			open 		= open,
			maxsize 	= maxsize,
		}
	}
	return message
end

Im not sure if this is correct, but according to the JS client, it looks right.
Id like to use this feature for a very simple way to join people together - seems like an ideal method.
I suspect a client side implementation may be required. If so, I'm happy to contribute if you can point me towards some info to do so - I have been looking at the JS code, so this may be the best starting point anyway.

SDK Updates

We need to investigate what the IAP support in Defold engine looks like as well as introduce the new session refresh APIs to the client object.

Creating a Match in Defold

I couldn't find Lua reference to create a Match (not a matchmaker) , i used the javascript api as refence .
To Create Match in js you have to use

var response = await socket.createMatch();
console.log("Created match with ID:", response.match.match_id);

You can share the match id with other players to join .
i want to do the same thing using Defold and Lua .
if i am not mistaken , the same should be in Lua :

local match_create_message = {
        match_create = {} -- empty , no arguments		
}
		
local match_result = nakama.socket_send(socket, match_create_message,callback)

This caused the server to disconnect with :

{"level":"warn","ts":"2021-01-04T11:23:32.727Z","msg":"Received malformed payload","uid":"51fc50d1-da4e-4f4c-b74a-ea60e75bad37","sid":"71335a58-9d9d-4d72-8cd4-3942df576269","data":"eyJjaWQiOiIxIiwibWF0Y2hfY3JlYXRlIjpbXX0="}

after decoding the data

{"cid":"1","match_create":[]}

Note :
A list with supported messages / arguments by nakama.socket_send() will be very helpful in these cases , the official documentation only covers creating a channel_join massage type example .

Is this Defold extension still active?

Hello,

I am planning to develop a game using Defiold and Nakama server. Still, I found out the latest release for this extension was in 2022 while other Nakama extensions for other engines (e.g. nakama-js) have several versions in 2023.

Is this defold extension still active and supports all Nakama features?

Authentication errors raise exceptions

Hi guys,

Started implementing user logins with custom usernames, needed to validate existing usernames, stumbled upon this:

DEBUG:SCRIPT: init()
DEBUG:SCRIPT: authenticate_device() with coroutine
DEBUG:SCRIPT: HTTP	POST	http://192.168.0.234:1750/v2/account/authenticate/device?create=true&username=Test
DEBUG:SCRIPT: DATA	{"id":"81a40de2-134c-4e82-ceb4-68545ab2cb63"}
DEBUG:SCRIPT: {"code":6,"message":"Username is already in use."}
ERROR:SCRIPT: /nakama/session.lua:17: You must provide a token
stack traceback:
  [C]:-1: in function assert
  /nakama/session.lua:17: in function create
  /nakama/nakama.lua:1756: in function callback
  /nakama/engine/defold.lua:74: in function </nakama/engine/defold.lua:68>

without debug enabled it just looks like an out-of-nowhere error although what I'm supposed to get is Username is already in use as a message returned.

Here, the response that the client gets from the server actually looks like this:

{
  status = 409,
  response = "{"code":6,"message":"Username is already in use."}",
  headers = {
    date = "Wed, 03 Feb 2021 19:41:38 GMT",
    content-length = "50",
    content-type = "application/json",
    cache-control = "no-store, no-cache, must-revalidate"
  }
}

but in the code (nakama/nakama.lua:1755) the client doesn't check for HTTP status code and relies on result.error property of the response (which is absent in the message):

return async(function(done)
    client.engine.http(client.config, url_path, query_params, "POST", post_data, function(result)
        if not result.error and api_session then 
            result = api_session.create(result)
        end
        done(result)
    end)
end)

Pretty much the same code is used across all the authenticate_* functions which means server responses like this one throw exceptions instead.

I believe the client should rely on the HTTP status code to determine if the response actually makes sense to continue authentication - plus, 409 is not the most common HTTP code which means Nakama devs are actually following the RFCs so the server responding with 200 or 201 is to be expected if the authentication has succeeded.

Really not sure what's wrong. Trying to send Match Data

trying to send match data with

local result = socket.match_data_send(matchid , opcodes.send_input, data)

this is being done in a coroutine with a connected socket.

No error, the server sees that the message is recieved like this

{"level":"debug","ts":"2024-07-10T17:18:31.705-0400","caller":"server/pipeline.go:65","msg":"Received *rtapi.Envelope_MatchDataSend message","uid":"2f1ac17d-b374-4aa2-85b1-34a73ee10af2","sid":"ea506ba6-3f01-11ef-b43b-e0e9830395f8","message":{"MatchDataSend":{"match_id":"89786132-d824-41e1-ad7d-820960662c7d.doomscroller","op_code":3,"data":"eyJ4IjoxLjA3MDY4ODAzMDE2MDIsInkiOjB9"}}}
I've tried both the lua and the typescript runtime, no dice. Essentially, the server receives the message but doesn't actually forward it to the match_loop handlers or anything. My only theory is that my match id is somehow wrong? I'm sending the same match_id that I save from the callback of the rpc that creates the match, its the same match_id as the one I use to successfully join. Then I try to send match data to that same match id, the server receives it but it never seems to make it to the match_loop. the messages table/object is literally empty. I have no idea what's causing this, can't find any similar issues and don't even know if this is something wrong with the client library or the runtimes or what I'm doing wrong.

Nakama authentication returns an error on iOS

Authenticating with Nakama returns this error when using nakama.authenticate_device(), nakama.authenticate_custom(), nakama.authenticate_apple(), nakama.authenticate_game_center() and nakama.authenticate_email() on iOS:

ERROR:SCRIPT: HTTP request to 'http://127.0.0.1:7350/v2/account/authenticate/apple?username=Niclas&create=true' failed (http result: -1  socket result: -24)
DEBUG:SCRIPT: 
{ --[[0x10b469ad0]]
  message = "Unable to decode response",
  error = true
} 

This line (and probably some other logs):
https://github.com/heroiclabs/nakama-defold/blob/master/nakama/engine/defold.lua#L67

Returns this:

DEBUG:SCRIPT: /v2/account/authenticate/device    with coroutine
DEBUG:SCRIPT: HTTP    POST    http://127.0.0.1:7350/v2/account/authenticate/device?username=Niclas&create=true
DEBUG:SCRIPT: DATA    {"id":"ccac5124-3e27-49c8-c93a-020000000000"}

Using nakama.authenticate_device(), nakama.authenticate_custom() and nakama.authenticate_email() works in the local Defold player on Mac.

"Received malformed payload" on match_data_send when upgrading to V3

Hello, I followed the Defold+Nakama tutorial and created a similar game, then tried to upgrade the libraries to V3 and started getting errors on the server with "Received malformed payload".

I had some differences in code and setup so I continued my testing on the xoxo sample project branch and got the same results. Tried with multiple versions of the Nakama server and same thing.

Here's the decoded payload the server was complaining about:

{"match_data_send":{"op_code":1,"data":"{\"row\":1,\"col\":1}","match_id":"1325d106-4ea1-495f-be90-f0d33d73777c.nakama1"},"cid":"3"}

When going back to main and enabling debug level on the server but I could only find this message:

{"level":"debug","ts":"2022-10-26T19:35:35.207Z","caller":"server/pipeline.go:66","msg":"Received *rtapi.Envelope_MatchDataSend message","uid":"3a59222c-9749-4100-869f-0fe975a72be0","sid":"4cb4ce7b-5565-11ed-9018-7106fdcb5b46","cid":"3","message":{"MatchDataSend":{"match_id":"da0d6d5e-9665-4943-90af-785050561cdd.nakama1","op_code":1,"data":"eyJyb3ciOjEsImNvbCI6M30="}}}

There are some visible differences:

  • the key of the message: match_data_send vsMatchDataSend
  • data: it's a json string on V3 and it's base64 in the older version

Not sure if they should match one to one though, could be a red herring.

Problem with WebSocket connections in HTML5 builds using wss://

I'm having problems receiving messages in HTML5 builds using the native WebSocket extension introduced in recent releases of Nakama-Defold.The same works well on desktop.

I'm able to connect to a Nakama Cloud hosted server and I can send a message (a matchmaker_add message), but I never get a response. I see that the socket starts receiving an empty message repeatedly after I send the first message.

The WebSocket extension can successfully send and receive data in an HTML5 build when connecting to wss://echo.websocket.org.

There's something different with the connection to the Nakama server. Any ideas? @novabyte?

@uncleNight have you successfully connected in an HTML5 build?

Sending match data does not invoke a response

No response is received when sending match data using the socket.match_data_send() method.

  • When socket.match_data_send() is called from a coroutine, the data is successfully sent to the server but no response is returned.
  • When socket.match_data_send() is called using a callback function, the data is successfully sent to the server but the callback function is never invoked.
  • The message was sent to a simple Authoritative match in Nakama server running in local development.

Sample project attached. The project is based on this repo. Sample code added to the example.script at the end of the init() lifecycle method.

nakama-defold-master - match-data-send-no-response.zip

image

Add option to auto-refresh sessions

Configurable similar to the Unity lib:

  • auto_refresh_session - Boolean value indicating if this feature is enabled, true by default
  • default_expired_timespan - The time prior to session expiry when auto-refresh will occur, set to 5 minutes be default

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.