summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/bullet/bullet_physics_server.cpp30
-rw-r--r--modules/bullet/bullet_physics_server.h3
-rw-r--r--modules/bullet/constraint_bullet.cpp12
-rw-r--r--modules/bullet/constraint_bullet.h4
-rw-r--r--modules/gdnative/SCsub60
-rw-r--r--modules/gdnative/doc_classes/NativeScript.xml38
-rw-r--r--modules/gdnative/gdnative_api.json103
-rw-r--r--modules/gdnative/include/gdnative/variant.h2
-rw-r--r--modules/gdnative/include/nativescript/godot_nativescript.h46
-rw-r--r--modules/gdnative/nativescript/godot_nativescript.cpp164
-rw-r--r--modules/gdnative/nativescript/nativescript.cpp216
-rw-r--r--modules/gdnative/nativescript/nativescript.h32
-rw-r--r--modules/gdnative/nativescript/register_types.cpp1
-rw-r--r--modules/mono/SCsub4
-rw-r--r--modules/openssl/stream_peer_openssl.cpp5
-rw-r--r--modules/websocket/SCsub70
-rw-r--r--modules/websocket/config.py7
-rw-r--r--modules/websocket/emws_client.cpp224
-rw-r--r--modules/websocket/emws_client.h62
-rw-r--r--modules/websocket/emws_peer.cpp173
-rw-r--r--modules/websocket/emws_peer.h85
-rw-r--r--modules/websocket/emws_server.cpp67
-rw-r--r--modules/websocket/emws_server.h58
-rw-r--r--modules/websocket/lws_client.cpp203
-rw-r--r--modules/websocket/lws_client.h61
-rw-r--r--modules/websocket/lws_helper.h214
-rw-r--r--modules/websocket/lws_peer.cpp200
-rw-r--r--modules/websocket/lws_peer.h92
-rw-r--r--modules/websocket/lws_server.cpp177
-rw-r--r--modules/websocket/lws_server.h63
-rw-r--r--modules/websocket/register_types.cpp79
-rw-r--r--modules/websocket/register_types.h31
-rw-r--r--modules/websocket/websocket_client.cpp124
-rw-r--r--modules/websocket/websocket_client.h68
-rw-r--r--modules/websocket/websocket_macros.h63
-rw-r--r--modules/websocket/websocket_multiplayer.cpp361
-rw-r--r--modules/websocket/websocket_multiplayer.h110
-rw-r--r--modules/websocket/websocket_peer.cpp49
-rw-r--r--modules/websocket/websocket_peer.h73
-rw-r--r--modules/websocket/websocket_server.cpp94
-rw-r--r--modules/websocket/websocket_server.h63
41 files changed, 3551 insertions, 40 deletions
diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp
index 51de4998fa..b646fc164d 100644
--- a/modules/bullet/bullet_physics_server.cpp
+++ b/modules/bullet/bullet_physics_server.cpp
@@ -70,8 +70,8 @@
return RID(); \
}
-#define AddJointToSpace(body, joint, disableCollisionsBetweenLinkedBodies) \
- body->get_space()->add_constraint(joint, disableCollisionsBetweenLinkedBodies);
+#define AddJointToSpace(body, joint) \
+ body->get_space()->add_constraint(joint, joint->is_disabled_collisions_between_bodies());
// <--------------- Joint creation asserts
btEmptyShape *BulletPhysicsServer::emptyShape(ShapeBullet::create_shape_empty());
@@ -987,6 +987,20 @@ int BulletPhysicsServer::joint_get_solver_priority(RID p_joint) const {
return 0;
}
+void BulletPhysicsServer::joint_disable_collisions_between_bodies(RID p_joint, const bool p_disable) {
+ JointBullet *joint = joint_owner.get(p_joint);
+ ERR_FAIL_COND(!joint);
+
+ joint->disable_collisions_between_bodies(p_disable);
+}
+
+bool BulletPhysicsServer::joint_is_disabled_collisions_between_bodies(RID p_joint) const {
+ JointBullet *joint(joint_owner.get(p_joint));
+ ERR_FAIL_COND_V(!joint, false);
+
+ return joint->is_disabled_collisions_between_bodies();
+}
+
RID BulletPhysicsServer::joint_create_pin(RID p_body_A, const Vector3 &p_local_A, RID p_body_B, const Vector3 &p_local_B) {
RigidBodyBullet *body_A = rigid_body_owner.get(p_body_A);
ERR_FAIL_COND_V(!body_A, RID());
@@ -1003,7 +1017,7 @@ RID BulletPhysicsServer::joint_create_pin(RID p_body_A, const Vector3 &p_local_A
ERR_FAIL_COND_V(body_A == body_B, RID());
JointBullet *joint = bulletnew(PinJointBullet(body_A, p_local_A, body_B, p_local_B));
- AddJointToSpace(body_A, joint, true);
+ AddJointToSpace(body_A, joint);
CreateThenReturnRID(joint_owner, joint);
}
@@ -1071,7 +1085,7 @@ RID BulletPhysicsServer::joint_create_hinge(RID p_body_A, const Transform &p_hin
ERR_FAIL_COND_V(body_A == body_B, RID());
JointBullet *joint = bulletnew(HingeJointBullet(body_A, body_B, p_hinge_A, p_hinge_B));
- AddJointToSpace(body_A, joint, true);
+ AddJointToSpace(body_A, joint);
CreateThenReturnRID(joint_owner, joint);
}
@@ -1091,7 +1105,7 @@ RID BulletPhysicsServer::joint_create_hinge_simple(RID p_body_A, const Vector3 &
ERR_FAIL_COND_V(body_A == body_B, RID());
JointBullet *joint = bulletnew(HingeJointBullet(body_A, body_B, p_pivot_A, p_pivot_B, p_axis_A, p_axis_B));
- AddJointToSpace(body_A, joint, true);
+ AddJointToSpace(body_A, joint);
CreateThenReturnRID(joint_owner, joint);
}
@@ -1143,7 +1157,7 @@ RID BulletPhysicsServer::joint_create_slider(RID p_body_A, const Transform &p_lo
ERR_FAIL_COND_V(body_A == body_B, RID());
JointBullet *joint = bulletnew(SliderJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B));
- AddJointToSpace(body_A, joint, true);
+ AddJointToSpace(body_A, joint);
CreateThenReturnRID(joint_owner, joint);
}
@@ -1177,7 +1191,7 @@ RID BulletPhysicsServer::joint_create_cone_twist(RID p_body_A, const Transform &
}
JointBullet *joint = bulletnew(ConeTwistJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B));
- AddJointToSpace(body_A, joint, true);
+ AddJointToSpace(body_A, joint);
CreateThenReturnRID(joint_owner, joint);
}
@@ -1213,7 +1227,7 @@ RID BulletPhysicsServer::joint_create_generic_6dof(RID p_body_A, const Transform
ERR_FAIL_COND_V(body_A == body_B, RID());
JointBullet *joint = bulletnew(Generic6DOFJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B, true));
- AddJointToSpace(body_A, joint, true);
+ AddJointToSpace(body_A, joint);
CreateThenReturnRID(joint_owner, joint);
}
diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h
index e0e46cd369..764ec2387c 100644
--- a/modules/bullet/bullet_physics_server.h
+++ b/modules/bullet/bullet_physics_server.h
@@ -290,6 +290,9 @@ public:
virtual void joint_set_solver_priority(RID p_joint, int p_priority);
virtual int joint_get_solver_priority(RID p_joint) const;
+ virtual void joint_disable_collisions_between_bodies(RID p_joint, const bool p_disable);
+ virtual bool joint_is_disabled_collisions_between_bodies(RID p_joint) const;
+
virtual RID joint_create_pin(RID p_body_A, const Vector3 &p_local_A, RID p_body_B, const Vector3 &p_local_B);
virtual void pin_joint_set_param(RID p_joint, PinJointParam p_param, float p_value);
diff --git a/modules/bullet/constraint_bullet.cpp b/modules/bullet/constraint_bullet.cpp
index b60e89b6fd..d15fb8de01 100644
--- a/modules/bullet/constraint_bullet.cpp
+++ b/modules/bullet/constraint_bullet.cpp
@@ -39,7 +39,8 @@
ConstraintBullet::ConstraintBullet() :
space(NULL),
- constraint(NULL) {}
+ constraint(NULL),
+ disabled_collisions_between_bodies(true) {}
void ConstraintBullet::setup(btTypedConstraint *p_constraint) {
constraint = p_constraint;
@@ -53,3 +54,12 @@ void ConstraintBullet::set_space(SpaceBullet *p_space) {
void ConstraintBullet::destroy_internal_constraint() {
space->remove_constraint(this);
}
+
+void ConstraintBullet::disable_collisions_between_bodies(const bool p_disabled) {
+ disabled_collisions_between_bodies = p_disabled;
+
+ if (space) {
+ space->remove_constraint(this);
+ space->add_constraint(this, disabled_collisions_between_bodies);
+ }
+}
diff --git a/modules/bullet/constraint_bullet.h b/modules/bullet/constraint_bullet.h
index 23be5a5063..ed3a318cbc 100644
--- a/modules/bullet/constraint_bullet.h
+++ b/modules/bullet/constraint_bullet.h
@@ -49,6 +49,7 @@ class ConstraintBullet : public RIDBullet {
protected:
SpaceBullet *space;
btTypedConstraint *constraint;
+ bool disabled_collisions_between_bodies;
public:
ConstraintBullet();
@@ -57,6 +58,9 @@ public:
virtual void set_space(SpaceBullet *p_space);
virtual void destroy_internal_constraint();
+ void disable_collisions_between_bodies(const bool p_disabled);
+ _FORCE_INLINE_ bool is_disabled_collisions_between_bodies() const { return disabled_collisions_between_bodies; }
+
public:
virtual ~ConstraintBullet() {
bulletdelete(constraint);
diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub
index c92c3f30a2..acfb83bc10 100644
--- a/modules/gdnative/SCsub
+++ b/modules/gdnative/SCsub
@@ -28,7 +28,7 @@ def _build_gdnative_api_struct_header(api):
'\textern const godot_gdnative_ext_{0}_api_struct *_gdnative_wrapper_{0}_api_struct;'.format(name))
gdnative_api_init_macro.append('\t_gdnative_wrapper_api_struct = options->api_struct;')
- gdnative_api_init_macro.append('\tfor (int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ')
+ gdnative_api_init_macro.append('\tfor (unsigned int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ')
gdnative_api_init_macro.append('\t\tswitch (_gdnative_wrapper_api_struct->extensions[i]->type) {')
for name in api['extensions']:
@@ -66,19 +66,30 @@ def _build_gdnative_api_struct_header(api):
out += ['};', '']
- for name in api['extensions']:
- out += [
- 'typedef struct godot_gdnative_ext_' + name + '_api_struct {',
+
+ def generate_extension_struct(name, ext, include_version=True):
+ ret_val = []
+ if ext['next']:
+ ret_val += generate_extension_struct(name, ext['next'])
+
+ ret_val += [
+ 'typedef struct godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct {',
'\tunsigned int type;',
'\tgodot_gdnative_api_version version;',
'\tconst godot_gdnative_api_struct *next;'
]
- for funcdef in api['extensions'][name]['api']:
+ for funcdef in ext['api']:
args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']])
- out.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args))
+ ret_val.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args))
+
+ ret_val += ['} godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct;', '']
+
+ return ret_val
- out += ['} godot_gdnative_ext_' + name + '_api_struct;', '']
+
+ for name in api['extensions']:
+ out += generate_extension_struct(name, api['extensions'][name], False)
out += [
'typedef struct godot_gdnative_core_api_struct {',
@@ -113,18 +124,35 @@ def _build_gdnative_api_struct_source(api):
''
]
- for name in api['extensions']:
- out += [
- 'extern const godot_gdnative_ext_' + name + '_api_struct api_extension_' + name + '_struct = {',
- '\tGDNATIVE_EXT_' + api['extensions'][name]['type'] + ',',
- '\t{' + str(api['extensions'][name]['version']['major']) + ', ' + str(api['extensions'][name]['version']['minor']) + '},',
- '\tNULL,'
+ def get_extension_struct_name(name, ext, include_version=True):
+ return 'godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct'
+
+ def get_extension_struct_instance_name(name, ext, include_version=True):
+ return 'api_extension_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_struct'
+
+ def get_extension_struct_definition(name, ext, include_version=True):
+
+ ret_val = []
+
+ if ext['next']:
+ ret_val += get_extension_struct_definition(name, ext['next'])
+
+ ret_val += [
+ 'extern const ' + get_extension_struct_name(name, ext, include_version) + ' ' + get_extension_struct_instance_name(name, ext, include_version) + ' = {',
+ '\tGDNATIVE_EXT_' + ext['type'] + ',',
+ '\t{' + str(ext['version']['major']) + ', ' + str(ext['version']['minor']) + '},',
+ '\t' + ('NULL' if not ext['next'] else ('(const godot_gdnative_api_struct *)&' + get_extension_struct_instance_name(name, ext['next']))) + ','
]
- for funcdef in api['extensions'][name]['api']:
- out.append('\t%s,' % funcdef['name'])
+ for funcdef in ext['api']:
+ ret_val.append('\t%s,' % funcdef['name'])
+
+ ret_val += ['};\n']
- out += ['};\n']
+ return ret_val
+
+ for name in api['extensions']:
+ out += get_extension_struct_definition(name, api['extensions'][name], False)
out += ['', 'const godot_gdnative_api_struct *gdnative_extensions_pointers[] = {']
diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml
index f713e4112e..6a71cd8d4d 100644
--- a/modules/gdnative/doc_classes/NativeScript.xml
+++ b/modules/gdnative/doc_classes/NativeScript.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="NativeScript" inherits="Script" category="Core" version="3.0-stable">
+<class name="NativeScript" inherits="Script" category="Core" version="3.1-dev">
<brief_description>
</brief_description>
<description>
@@ -9,10 +9,46 @@
<demos>
</demos>
<methods>
+ <method name="get_class_documentation" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ Returns the documentation string that was previously set with [code]godot_nativescript_set_class_documentation[/code].
+ </description>
+ </method>
+ <method name="get_method_documentation" qualifiers="const">
+ <return type="String">
+ </return>
+ <argument index="0" name="method" type="String">
+ </argument>
+ <description>
+ Returns the documentation string that was previously set with [code]godot_nativescript_set_method_documentation[/code].
+ </description>
+ </method>
+ <method name="get_property_documentation" qualifiers="const">
+ <return type="String">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <description>
+ Returns the documentation string that was previously set with [code]godot_nativescript_set_property_documentation[/code].
+ </description>
+ </method>
+ <method name="get_signal_documentation" qualifiers="const">
+ <return type="String">
+ </return>
+ <argument index="0" name="signal_name" type="String">
+ </argument>
+ <description>
+ Returns the documentation string that was previously set with [code]godot_nativescript_set_signal_documentation[/code].
+ </description>
+ </method>
<method name="new" qualifiers="vararg">
<return type="Object">
</return>
<description>
+ Constructs a new object of the base type with a script of this type already attached.
+ [i]Note[/i]: Any arguments passed to this function will be ignored and not passed to the native constructor function. This will change with in a future API extension.
</description>
</method>
</methods>
diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json
index 59a9c0b090..a8919f7130 100644
--- a/modules/gdnative/gdnative_api.json
+++ b/modules/gdnative/gdnative_api.json
@@ -5,6 +5,7 @@
"major": 1,
"minor": 0
},
+ "next": null,
"api": [
{
"name": "godot_color_new_rgba",
@@ -3963,7 +3964,7 @@
"name": "godot_variant_new_bool",
"return_type": "void",
"arguments": [
- ["godot_variant *", "p_v"],
+ ["godot_variant *", "r_dest"],
["const godot_bool", "p_b"]
]
},
@@ -5762,6 +5763,104 @@
"major": 1,
"minor": 0
},
+ "next": {
+ "type": "NATIVESCRIPT",
+ "version": {
+ "major": 1,
+ "minor": 1
+ },
+ "next": null,
+ "api": [
+ {
+ "name": "godot_nativescript_set_method_argument_information",
+ "return_type": "void",
+ "arguments": [
+ ["void *", "p_gdnative_handle"],
+ ["const char *", "p_name"],
+ ["const char *", "p_function_name"],
+ ["int", "p_num_args"],
+ ["const godot_method_arg *", "p_args"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_set_class_documentation",
+ "return_type": "void",
+ "arguments": [
+ ["void *", "p_gdnative_handle"],
+ ["const char *", "p_name"],
+ ["godot_string", "p_documentation"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_set_method_documentation",
+ "return_type": "void",
+ "arguments": [
+ ["void *", "p_gdnative_handle"],
+ ["const char *", "p_name"],
+ ["const char *", "p_function_name"],
+ ["godot_string", "p_documentation"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_set_property_documentation",
+ "return_type": "void",
+ "arguments": [
+ ["void *", "p_gdnative_handle"],
+ ["const char *", "p_name"],
+ ["const char *", "p_path"],
+ ["godot_string", "p_documentation"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_set_signal_documentation",
+ "return_type": "void",
+ "arguments": [
+ ["void *", "p_gdnative_handle"],
+ ["const char *", "p_name"],
+ ["const char *", "p_signal_name"],
+ ["godot_string", "p_documentation"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_set_type_tag",
+ "return_type": "void",
+ "arguments": [
+ ["void *", "p_gdnative_handle"],
+ ["const char *", "p_name"],
+ ["const void *", "p_type_tag"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_get_type_tag",
+ "return_type": "const void *",
+ "arguments": [
+ ["const godot_object *", "p_object"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_register_instance_binding_data_functions",
+ "return_type": "int",
+ "arguments": [
+ ["godot_instance_binding_functions", "p_binding_functions"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_unregister_instance_binding_data_functions",
+ "return_type": "void",
+ "arguments": [
+ ["int", "p_idx"]
+ ]
+ },
+ {
+ "name": "godot_nativescript_get_instance_binding_data",
+ "return_type": "void *",
+ "arguments": [
+ ["int", "p_idx"],
+ ["godot_object *", "p_object"]
+ ]
+ }
+ ]
+ },
"api": [
{
"name": "godot_nativescript_register_class",
@@ -5832,6 +5931,7 @@
"major": 1,
"minor": 0
},
+ "next": null,
"api": [
{
"name": "godot_pluginscript_register_language",
@@ -5848,6 +5948,7 @@
"major": 1,
"minor": 0
},
+ "next": null,
"api": [
{
"name": "godot_arvr_register_interface",
diff --git a/modules/gdnative/include/gdnative/variant.h b/modules/gdnative/include/gdnative/variant.h
index d2e8246bfb..6779dc4092 100644
--- a/modules/gdnative/include/gdnative/variant.h
+++ b/modules/gdnative/include/gdnative/variant.h
@@ -135,7 +135,7 @@ void GDAPI godot_variant_new_copy(godot_variant *r_dest, const godot_variant *p_
void GDAPI godot_variant_new_nil(godot_variant *r_dest);
-void GDAPI godot_variant_new_bool(godot_variant *p_v, const godot_bool p_b);
+void GDAPI godot_variant_new_bool(godot_variant *r_dest, const godot_bool p_b);
void GDAPI godot_variant_new_uint(godot_variant *r_dest, const uint64_t p_i);
void GDAPI godot_variant_new_int(godot_variant *r_dest, const int64_t p_i);
void GDAPI godot_variant_new_real(godot_variant *r_dest, const double p_r);
diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h
index 11017ae78d..747328bc41 100644
--- a/modules/gdnative/include/nativescript/godot_nativescript.h
+++ b/modules/gdnative/include/nativescript/godot_nativescript.h
@@ -185,6 +185,52 @@ void GDAPI godot_nativescript_register_signal(void *p_gdnative_handle, const cha
void GDAPI *godot_nativescript_get_userdata(godot_object *p_instance);
+/*
+ *
+ *
+ * NativeScript 1.1
+ *
+ *
+ */
+
+// method registering with argument names
+
+typedef struct {
+ godot_string name;
+
+ godot_variant_type type;
+ godot_property_hint hint;
+ godot_string hint_string;
+} godot_method_arg;
+
+void GDAPI godot_nativescript_set_method_argument_information(void *p_gdnative_handle, const char *p_name, const char *p_function_name, int p_num_args, const godot_method_arg *p_args);
+
+// documentation
+
+void GDAPI godot_nativescript_set_class_documentation(void *p_gdnative_handle, const char *p_name, godot_string p_documentation);
+void GDAPI godot_nativescript_set_method_documentation(void *p_gdnative_handle, const char *p_name, const char *p_function_name, godot_string p_documentation);
+void GDAPI godot_nativescript_set_property_documentation(void *p_gdnative_handle, const char *p_name, const char *p_path, godot_string p_documentation);
+void GDAPI godot_nativescript_set_signal_documentation(void *p_gdnative_handle, const char *p_name, const char *p_signal_name, godot_string p_documentation);
+
+// type tag API
+
+void GDAPI godot_nativescript_set_type_tag(void *p_gdnative_handle, const char *p_name, const void *p_type_tag);
+const void GDAPI *godot_nativescript_get_type_tag(const godot_object *p_object);
+
+// instance binding API
+
+typedef struct {
+ void *(*alloc_instance_binding_data)(void *, godot_object *);
+ void (*free_instance_binding_data)(void *, void *);
+ void *data;
+ void (*free_func)(void *);
+} godot_instance_binding_functions;
+
+int GDAPI godot_nativescript_register_instance_binding_data_functions(godot_instance_binding_functions p_binding_functions);
+void GDAPI godot_nativescript_unregister_instance_binding_data_functions(int p_idx);
+
+void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object *p_object);
+
#ifdef __cplusplus
}
#endif
diff --git a/modules/gdnative/nativescript/godot_nativescript.cpp b/modules/gdnative/nativescript/godot_nativescript.cpp
index b4f7e1555e..aea595d0f0 100644
--- a/modules/gdnative/nativescript/godot_nativescript.cpp
+++ b/modules/gdnative/nativescript/godot_nativescript.cpp
@@ -106,7 +106,7 @@ void GDAPI godot_nativescript_register_method(void *p_gdnative_handle, const cha
Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
if (!E) {
- ERR_EXPLAIN("Attempt to register method on non-existant class!");
+ ERR_EXPLAIN("Attempted to register method on non-existent class!");
ERR_FAIL();
}
@@ -125,7 +125,7 @@ void GDAPI godot_nativescript_register_property(void *p_gdnative_handle, const c
Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
if (!E) {
- ERR_EXPLAIN("Attempt to register method on non-existant class!");
+ ERR_EXPLAIN("Attempted to register method on non-existent class!");
ERR_FAIL();
}
@@ -150,7 +150,7 @@ void GDAPI godot_nativescript_register_signal(void *p_gdnative_handle, const cha
Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
if (!E) {
- ERR_EXPLAIN("Attempt to register method on non-existant class!");
+ ERR_EXPLAIN("Attempted to register method on non-existent class!");
ERR_FAIL();
}
@@ -201,6 +201,164 @@ void GDAPI *godot_nativescript_get_userdata(godot_object *p_instance) {
return NULL;
}
+/*
+ *
+ *
+ * NativeScript 1.1
+ *
+ *
+ */
+
+void GDAPI godot_nativescript_set_method_argument_information(void *p_gdnative_handle, const char *p_name, const char *p_function_name, int p_num_args, const godot_method_arg *p_args) {
+ String *s = (String *)p_gdnative_handle;
+
+ Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
+
+ if (!E) {
+ ERR_EXPLAIN("Attempted to add argument information for a method on a non-existent class!");
+ ERR_FAIL();
+ }
+
+ Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name);
+ if (!method) {
+ ERR_EXPLAIN("Attempted to add argument information to non-existent method!");
+ ERR_FAIL();
+ }
+
+ MethodInfo *method_information = &method->get().info;
+
+ List<PropertyInfo> args;
+
+ for (int i = 0; i < p_num_args; i++) {
+ godot_method_arg arg = p_args[i];
+ String name = *(String *)&arg.name;
+ String hint_string = *(String *)&arg.hint_string;
+
+ Variant::Type type = (Variant::Type)arg.type;
+ PropertyHint hint = (PropertyHint)arg.hint;
+
+ args.push_back(PropertyInfo(type, p_name, hint, hint_string));
+ }
+
+ method_information->arguments = args;
+}
+
+void GDAPI godot_nativescript_set_class_documentation(void *p_gdnative_handle, const char *p_name, godot_string p_documentation) {
+ String *s = (String *)p_gdnative_handle;
+
+ Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
+
+ if (!E) {
+ ERR_EXPLAIN("Attempted to add documentation to a non-existent class!");
+ ERR_FAIL();
+ }
+
+ E->get().documentation = *(String *)&p_documentation;
+}
+
+void GDAPI godot_nativescript_set_method_documentation(void *p_gdnative_handle, const char *p_name, const char *p_function_name, godot_string p_documentation) {
+ String *s = (String *)p_gdnative_handle;
+
+ Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
+
+ if (!E) {
+ ERR_EXPLAIN("Attempted to add documentation to a method on a non-existent class!");
+ ERR_FAIL();
+ }
+
+ Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name);
+ if (!method) {
+ ERR_EXPLAIN("Attempted to add documentatino to non-existent method!");
+ ERR_FAIL();
+ }
+
+ method->get().documentation = *(String *)&p_documentation;
+}
+
+void GDAPI godot_nativescript_set_property_documentation(void *p_gdnative_handle, const char *p_name, const char *p_path, godot_string p_documentation) {
+ String *s = (String *)p_gdnative_handle;
+
+ Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
+
+ if (!E) {
+ ERR_EXPLAIN("Attempted to add documentation to a property on a non-existent class!");
+ ERR_FAIL();
+ }
+
+ OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = E->get().properties.find(p_path);
+ if (!property) {
+ ERR_EXPLAIN("Attempted to add documentation to non-existent property!");
+ ERR_FAIL();
+ }
+
+ property.get().documentation = *(String *)&p_documentation;
+}
+
+void GDAPI godot_nativescript_set_signal_documentation(void *p_gdnative_handle, const char *p_name, const char *p_signal_name, godot_string p_documentation) {
+ String *s = (String *)p_gdnative_handle;
+
+ Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
+
+ if (!E) {
+ ERR_EXPLAIN("Attempted to add documentation to a signal on a non-existent class!");
+ ERR_FAIL();
+ }
+
+ Map<StringName, NativeScriptDesc::Signal>::Element *signal = E->get().signals_.find(p_signal_name);
+ if (!signal) {
+ ERR_EXPLAIN("Attempted to add documentation to non-existent signal!");
+ ERR_FAIL();
+ }
+
+ signal->get().documentation = *(String *)&p_documentation;
+}
+
+void GDAPI godot_nativescript_set_type_tag(void *p_gdnative_handle, const char *p_name, const void *p_type_tag) {
+ String *s = (String *)p_gdnative_handle;
+
+ Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name);
+
+ if (!E) {
+ ERR_EXPLAIN("Attempted to set type tag on a non-existent class!");
+ ERR_FAIL();
+ }
+
+ E->get().type_tag = p_type_tag;
+}
+
+const void GDAPI *godot_nativescript_get_type_tag(const godot_object *p_object) {
+
+ const Object *o = (Object *)p_object;
+
+ if (!o->get_script_instance()) {
+ ERR_EXPLAIN("Attempted to get type tag on an object without a script!");
+ ERR_FAIL_V(NULL);
+ } else {
+ NativeScript *script = Object::cast_to<NativeScript>(o->get_script_instance()->get_script().ptr());
+ if (!script) {
+ ERR_EXPLAIN("Attempted to get type tag on an object without a nativescript attached");
+ ERR_FAIL_V(NULL);
+ }
+
+ if (script->get_script_desc())
+ return script->get_script_desc()->type_tag;
+ }
+
+ return NULL;
+}
+
#ifdef __cplusplus
}
#endif
+
+int GDAPI godot_nativescript_register_instance_binding_data_functions(godot_instance_binding_functions p_binding_functions) {
+ return NativeScriptLanguage::get_singleton()->register_binding_functions(p_binding_functions);
+}
+
+void GDAPI godot_nativescript_unregister_instance_binding_data_functions(int p_idx) {
+ NativeScriptLanguage::get_singleton()->unregister_binding_functions(p_idx);
+}
+
+void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object *p_object) {
+ return NativeScriptLanguage::get_singleton()->get_instance_binding_data(p_idx, (Object *)p_object);
+}
diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp
index aaa7d634d1..f2e9bef467 100644
--- a/modules/gdnative/nativescript/nativescript.cpp
+++ b/modules/gdnative/nativescript/nativescript.cpp
@@ -68,6 +68,11 @@ void NativeScript::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_library", "library"), &NativeScript::set_library);
ClassDB::bind_method(D_METHOD("get_library"), &NativeScript::get_library);
+ ClassDB::bind_method(D_METHOD("get_class_documentation"), &NativeScript::get_class_documentation);
+ ClassDB::bind_method(D_METHOD("get_method_documentation", "method"), &NativeScript::get_method_documentation);
+ ClassDB::bind_method(D_METHOD("get_signal_documentation", "signal_name"), &NativeScript::get_signal_documentation);
+ ClassDB::bind_method(D_METHOD("get_property_documentation", "path"), &NativeScript::get_property_documentation);
+
ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name");
ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library");
@@ -373,6 +378,86 @@ void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const {
}
}
+String NativeScript::get_class_documentation() const {
+ NativeScriptDesc *script_data = get_script_desc();
+
+ if (!script_data) {
+ ERR_EXPLAIN("Attempt to get class documentation on invalid NativeScript");
+ ERR_FAIL_V("");
+ }
+
+ return script_data->documentation;
+}
+
+String NativeScript::get_method_documentation(const StringName &p_method) const {
+ NativeScriptDesc *script_data = get_script_desc();
+
+ if (!script_data) {
+ ERR_EXPLAIN("Attempt to get method documentation on invalid NativeScript");
+ ERR_FAIL_V("");
+ }
+
+ while (script_data) {
+
+ Map<StringName, NativeScriptDesc::Method>::Element *method = script_data->methods.find(p_method);
+
+ if (method) {
+ return method->get().documentation;
+ }
+
+ script_data = script_data->base_data;
+ }
+
+ ERR_EXPLAIN("Attempt to get method documentation for non-existent method");
+ ERR_FAIL_V("");
+}
+
+String NativeScript::get_signal_documentation(const StringName &p_signal_name) const {
+ NativeScriptDesc *script_data = get_script_desc();
+
+ if (!script_data) {
+ ERR_EXPLAIN("Attempt to get signal documentation on invalid NativeScript");
+ ERR_FAIL_V("");
+ }
+
+ while (script_data) {
+
+ Map<StringName, NativeScriptDesc::Signal>::Element *signal = script_data->signals_.find(p_signal_name);
+
+ if (signal) {
+ return signal->get().documentation;
+ }
+
+ script_data = script_data->base_data;
+ }
+
+ ERR_EXPLAIN("Attempt to get signal documentation for non-existent signal");
+ ERR_FAIL_V("");
+}
+
+String NativeScript::get_property_documentation(const StringName &p_path) const {
+ NativeScriptDesc *script_data = get_script_desc();
+
+ if (!script_data) {
+ ERR_EXPLAIN("Attempt to get property documentation on invalid NativeScript");
+ ERR_FAIL_V("");
+ }
+
+ while (script_data) {
+
+ OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = script_data->properties.find(p_path);
+
+ if (property) {
+ return property.get().documentation;
+ }
+
+ script_data = script_data->base_data;
+ }
+
+ ERR_EXPLAIN("Attempt to get property documentation for non-existent signal");
+ ERR_FAIL_V("");
+}
+
Variant NativeScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
if (lib_path.empty() || class_name.empty() || library.is_null()) {
@@ -610,7 +695,7 @@ Variant::Type NativeScriptInstance::get_property_type(const StringName &p_name,
}
void NativeScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
- script->get_method_list(p_list);
+ script->get_script_method_list(p_list);
}
bool NativeScriptInstance::has_method(const StringName &p_method) const {
@@ -824,6 +909,25 @@ void NativeScriptLanguage::_unload_stuff(bool p_reload) {
}
}
+ Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path);
+ Ref<GDNative> gdn;
+
+ if (E) {
+ gdn = E->get();
+ }
+
+ if (gdn.is_valid() && gdn->get_library().is_valid()) {
+ Ref<GDNativeLibrary> lib = gdn->get_library();
+ void *terminate_fn;
+ Error err = gdn->get_symbol(lib->get_symbol_prefix() + _terminate_call_name, terminate_fn, true);
+
+ if (err == OK) {
+ void (*terminate)(void *) = (void (*)(void *))terminate_fn;
+
+ terminate((void *)&lib_path);
+ }
+ }
+
for (Map<StringName, NativeScriptDesc>::Element *C = classes.front(); C; C = C->next()) {
// free property stuff first
@@ -1011,6 +1115,116 @@ int NativeScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, in
return 0;
}
+int NativeScriptLanguage::register_binding_functions(godot_instance_binding_functions p_binding_functions) {
+
+ // find index
+
+ int idx = -1;
+
+ for (int i = 0; i < binding_functions.size(); i++) {
+ if (!binding_functions[i].first) {
+ // free, we'll take it
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx == -1) {
+ idx = binding_functions.size();
+ binding_functions.resize(idx + 1);
+ }
+
+ // set the functions
+ binding_functions[idx].first = true;
+ binding_functions[idx].second = p_binding_functions;
+
+ return idx;
+}
+
+void NativeScriptLanguage::unregister_binding_functions(int p_idx) {
+ ERR_FAIL_INDEX(p_idx, binding_functions.size());
+
+ for (Set<Vector<void *> *>::Element *E = binding_instances.front(); E; E = E->next()) {
+ Vector<void *> &binding_data = *E->get();
+
+ if (binding_data[p_idx] && binding_functions[p_idx].second.free_instance_binding_data)
+ binding_functions[p_idx].second.free_instance_binding_data(binding_functions[p_idx].second.data, binding_data[p_idx]);
+ }
+
+ binding_functions[p_idx].first = false;
+
+ if (binding_functions[p_idx].second.free_func)
+ binding_functions[p_idx].second.free_func(binding_functions[p_idx].second.data);
+}
+
+void *NativeScriptLanguage::get_instance_binding_data(int p_idx, Object *p_object) {
+ ERR_FAIL_INDEX_V(p_idx, binding_functions.size(), NULL);
+
+ if (!binding_functions[p_idx].first) {
+ ERR_EXPLAIN("Tried to get binding data for a nativescript binding that does not exist");
+ ERR_FAIL_V(NULL);
+ }
+
+ Vector<void *> *binding_data = (Vector<void *> *)p_object->get_script_instance_binding(lang_idx);
+
+ if (!binding_data)
+ return NULL; // should never happen.
+
+ if (binding_data->size() <= p_idx) {
+ // okay, add new elements here.
+ int old_size = binding_data->size();
+
+ binding_data->resize(p_idx + 1);
+
+ for (int i = old_size; i <= p_idx; i++) {
+ (*binding_data)[i] = NULL;
+ }
+ }
+
+ if (!(*binding_data)[p_idx]) {
+ // no binding data yet, soooooo alloc new one \o/
+ (*binding_data)[p_idx] = binding_functions[p_idx].second.alloc_instance_binding_data(binding_functions[p_idx].second.data, (godot_object *)p_object);
+ }
+
+ return (*binding_data)[p_idx];
+}
+
+void *NativeScriptLanguage::alloc_instance_binding_data(Object *p_object) {
+
+ Vector<void *> *binding_data = new Vector<void *>;
+
+ binding_data->resize(binding_functions.size());
+
+ for (int i = 0; i < binding_functions.size(); i++) {
+ (*binding_data)[i] = NULL;
+ }
+
+ binding_instances.insert(binding_data);
+
+ return (void *)binding_data;
+}
+
+void NativeScriptLanguage::free_instance_binding_data(void *p_data) {
+
+ if (!p_data)
+ return;
+
+ Vector<void *> &binding_data = *(Vector<void *> *)p_data;
+
+ for (int i = 0; i < binding_data.size(); i++) {
+ if (!binding_data[i])
+ continue;
+
+ if (binding_functions[i].first && binding_functions[i].second.free_instance_binding_data) {
+ binding_functions[i].second.free_instance_binding_data(binding_functions[i].second.data, binding_data[i]);
+ }
+ }
+
+ binding_instances.erase(&binding_data);
+
+ delete &binding_data;
+}
+
#ifndef NO_THREADS
void NativeScriptLanguage::defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script) {
MutexLock lock(mutex);
diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h
index ac94c84bc4..17b6ddc747 100644
--- a/modules/gdnative/nativescript/nativescript.h
+++ b/modules/gdnative/nativescript/nativescript.h
@@ -53,6 +53,7 @@ struct NativeScriptDesc {
godot_instance_method method;
MethodInfo info;
int rpc_mode;
+ String documentation;
};
struct Property {
godot_property_set_func setter;
@@ -60,12 +61,16 @@ struct NativeScriptDesc {
PropertyInfo info;
Variant default_value;
int rset_mode;
+ String documentation;
};
struct Signal {
MethodInfo signal;
+ String documentation;
};
+ String documentation;
+
Map<StringName, Method> methods;
OrderedHashMap<StringName, Property> properties;
Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals
@@ -75,6 +80,8 @@ struct NativeScriptDesc {
godot_instance_create_func create_func;
godot_instance_destroy_func destroy_func;
+ const void *type_tag;
+
bool is_tool;
inline NativeScriptDesc() :
@@ -82,7 +89,9 @@ struct NativeScriptDesc {
properties(),
signals_(),
base(),
- base_native_type() {
+ base_native_type(),
+ documentation(),
+ type_tag(NULL) {
zeromem(&create_func, sizeof(godot_instance_create_func));
zeromem(&destroy_func, sizeof(godot_instance_destroy_func));
}
@@ -154,6 +163,11 @@ public:
virtual void get_script_method_list(List<MethodInfo> *p_list) const;
virtual void get_script_property_list(List<PropertyInfo> *p_list) const;
+ String get_class_documentation() const;
+ String get_method_documentation(const StringName &p_method) const;
+ String get_signal_documentation(const StringName &p_signal_name) const;
+ String get_property_documentation(const StringName &p_path) const;
+
Variant _new(const Variant **p_args, int p_argcount, Variant::CallError &r_error);
NativeScript();
@@ -204,6 +218,7 @@ class NativeScriptLanguage : public ScriptLanguage {
private:
static NativeScriptLanguage *singleton;
+ int lang_idx;
void _unload_stuff(bool p_reload = false);
@@ -222,6 +237,9 @@ private:
void call_libraries_cb(const StringName &name);
+ Vector<Pair<bool, godot_instance_binding_functions> > binding_functions;
+ Set<Vector<void *> *> binding_instances;
+
public:
// These two maps must only be touched on the main thread
Map<String, Map<StringName, NativeScriptDesc> > library_classes;
@@ -232,6 +250,8 @@ public:
const StringName _init_call_type = "nativescript_init";
const StringName _init_call_name = "nativescript_init";
+ const StringName _terminate_call_name = "nativescript_terminate";
+
const StringName _noarg_call_type = "nativescript_no_arg";
const StringName _frame_call_name = "nativescript_frame";
@@ -250,6 +270,8 @@ public:
void _hacky_api_anchor();
+ _FORCE_INLINE_ void set_language_index(int p_idx) { lang_idx = p_idx; }
+
#ifndef NO_THREADS
virtual void thread_enter();
virtual void thread_exit();
@@ -293,6 +315,14 @@ public:
virtual void profiling_stop();
virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max);
virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max);
+
+ int register_binding_functions(godot_instance_binding_functions p_binding_functions);
+ void unregister_binding_functions(int p_idx);
+
+ void *get_instance_binding_data(int p_idx, Object *p_object);
+
+ virtual void *alloc_instance_binding_data(Object *p_object);
+ virtual void free_instance_binding_data(void *p_data);
};
inline NativeScriptDesc *NativeScript::get_script_desc() const {
diff --git a/modules/gdnative/nativescript/register_types.cpp b/modules/gdnative/nativescript/register_types.cpp
index cb55a13b3e..9a0e764391 100644
--- a/modules/gdnative/nativescript/register_types.cpp
+++ b/modules/gdnative/nativescript/register_types.cpp
@@ -47,6 +47,7 @@ void register_nativescript_types() {
ClassDB::register_class<NativeScript>();
+ native_script_language->set_language_index(ScriptServer::get_language_count());
ScriptServer::register_language(native_script_language);
resource_saver_gdns = memnew(ResourceFormatSaverNativeScript);
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index 320bbe7090..aa8626e6da 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -57,10 +57,10 @@ if env['tools']:
vars = Variables()
vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True))
vars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False))
-vars.Update(env)
+vars.Update(env_mono)
# Glue sources
-if env['mono_glue']:
+if env_mono['mono_glue']:
env_mono.add_source_files(env.modules_sources, 'glue/*.cpp')
else:
env_mono.Append(CPPDEFINES=['MONO_GLUE_DISABLED'])
diff --git a/modules/openssl/stream_peer_openssl.cpp b/modules/openssl/stream_peer_openssl.cpp
index e3cb9bbdf8..84c4e85006 100644
--- a/modules/openssl/stream_peer_openssl.cpp
+++ b/modules/openssl/stream_peer_openssl.cpp
@@ -141,9 +141,6 @@ int StreamPeerOpenSSL::_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg
X509_NAME_oneline(X509_get_subject_name(server_cert),
cert_str, sizeof(cert_str));
- print_line("CERT STR: " + String(cert_str));
- print_line("VALID: " + itos(base_cert_valid));
-
if (!base_cert_valid)
return 0;
@@ -382,7 +379,6 @@ Error StreamPeerOpenSSL::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida
// Same as before, try to connect.
int result = SSL_connect(ssl);
- print_line("CONNECTION RESULT: " + itos(result));
if (result < 1) {
ERR_print_errors_fp(stdout);
_print_error(result);
@@ -392,7 +388,6 @@ Error StreamPeerOpenSSL::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida
if (peer) {
bool cert_ok = SSL_get_verify_result(ssl) == X509_V_OK;
- print_line("cert_ok: " + itos(cert_ok));
} else if (validate_certs) {
status = STATUS_ERROR_NO_CERTIFICATE;
diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub
new file mode 100644
index 0000000000..067a99ffff
--- /dev/null
+++ b/modules/websocket/SCsub
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+Import('env')
+Import('env_modules')
+
+# Thirdparty source files
+
+env_lws = env_modules.Clone()
+
+thirdparty_dir = "#thirdparty/lws/"
+helper_dir = "win32helpers/"
+openssl_dir = "#thirdparty/openssl/"
+thirdparty_sources = [
+ "client/client.c",
+ "client/client-handshake.c",
+ "client/client-parser.c",
+ "client/ssl-client.c",
+
+ "ext/extension.c",
+ "ext/extension-permessage-deflate.c",
+
+ "server/fops-zip.c",
+ "server/lejp-conf.c",
+ "server/parsers.c",
+ "server/ranges.c",
+ "server/server.c",
+ "server/server-handshake.c",
+ "server/ssl-server.c",
+
+ "misc/base64-decode.c",
+ "misc/lejp.c",
+ "misc/sha-1.c",
+
+ "alloc.c",
+ "context.c",
+ "handshake.c",
+ "header.c",
+ "libwebsockets.c",
+ "minilex.c",
+ "output.c",
+ "pollfd.c",
+ "service.c",
+ "ssl.c",
+
+]
+
+if env_lws["platform"] == "android": # Builtin getifaddrs
+ thirdparty_sources += ["misc/getifaddrs.c"]
+
+if env_lws["platform"] == "windows": # Winsock
+ thirdparty_sources += ["plat/lws-plat-win.c", helper_dir + "getopt.c", helper_dir + "getopt_long.c", helper_dir + "gettimeofday.c"]
+else: # Unix socket
+ thirdparty_sources += ["plat/lws-plat-unix.c"]
+
+
+thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
+
+if env_lws["platform"] == "javascript": # No need to add third party libraries at all
+ pass
+else:
+ env_lws.add_source_files(env.modules_sources, thirdparty_sources)
+ env_lws.Append(CPPPATH=[thirdparty_dir])
+
+ if env['builtin_openssl']:
+ env_lws.Append(CPPPATH=[openssl_dir])
+
+ if env_lws["platform"] == "windows":
+ env_lws.Append(CPPPATH=[thirdparty_dir + helper_dir])
+
+env_lws.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/websocket/config.py b/modules/websocket/config.py
new file mode 100644
index 0000000000..fb920482f5
--- /dev/null
+++ b/modules/websocket/config.py
@@ -0,0 +1,7 @@
+
+def can_build(platform):
+ return True
+
+
+def configure(env):
+ pass
diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp
new file mode 100644
index 0000000000..38fe520fc1
--- /dev/null
+++ b/modules/websocket/emws_client.cpp
@@ -0,0 +1,224 @@
+/*************************************************************************/
+/* emws_client.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifdef JAVASCRIPT_ENABLED
+
+#include "emws_client.h"
+#include "core/io/ip.h"
+#include "emscripten.h"
+
+extern "C" {
+EMSCRIPTEN_KEEPALIVE void _esws_on_connect(void *obj, char *proto) {
+ EMWSClient *client = static_cast<EMWSClient *>(obj);
+ client->_is_connecting = false;
+ client->_on_connect(String(proto));
+}
+
+EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_data_size, int p_is_string) {
+ EMWSClient *client = static_cast<EMWSClient *>(obj);
+
+ static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1);
+ client->_on_peer_packet();
+}
+
+EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) {
+ EMWSClient *client = static_cast<EMWSClient *>(obj);
+ client->_is_connecting = false;
+ client->_on_error();
+}
+
+EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int was_clean) {
+ EMWSClient *client = static_cast<EMWSClient *>(obj);
+ client->_is_connecting = false;
+ client->_on_disconnect();
+}
+}
+
+Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) {
+
+ String str = "ws://";
+ String proto_string = "";
+ int i = 0;
+
+ if (p_ssl)
+ str = "wss://";
+ str += p_host + ":" + itos(p_port) + p_path;
+ for (int i = 0; i < p_protocols.size(); i++) {
+ proto_string += p_protocols[i];
+ proto_string += ",";
+ }
+ if (proto_string == "")
+ proto_string = "binary,";
+
+ proto_string = proto_string.substr(0, proto_string.length() - 1);
+
+ _is_connecting = true;
+ /* clang-format off */
+ int peer_sock = EM_ASM_INT({
+ var socket = new WebSocket(UTF8ToString($1), UTF8ToString($2).split(","));
+ var c_ptr = Module.IDHandler.get($0);
+ socket.binaryType = "arraybuffer";
+
+ // Connection opened
+ socket.addEventListener("open", function (event) {
+ if (!Module.IDHandler.has($0))
+ return; // Godot Object is gone!
+ ccall("_esws_on_connect",
+ "void",
+ ["number", "string"],
+ [c_ptr, socket.protocol]
+ );
+ });
+
+ // Listen for messages
+ socket.addEventListener("message", function (event) {
+ if (!Module.IDHandler.has($0))
+ return; // Godot Object is gone!
+ var buffer;
+ var is_string = 0;
+ if (event.data instanceof ArrayBuffer) {
+
+ buffer = new Uint8Array(event.data);
+
+ } else if (event.data instanceof Blob) {
+
+ alert("Blob type not supported");
+ return;
+
+ } else if (typeof event.data === "string") {
+
+ is_string = 1;
+ var enc = new TextEncoder("utf-8");
+ buffer = new Uint8Array(enc.encode(event.data));
+
+ } else {
+
+ alert("Unknown message type");
+ return;
+
+ }
+ var len = buffer.length*buffer.BYTES_PER_ELEMENT;
+ var out = Module._malloc(len);
+ Module.HEAPU8.set(buffer, out);
+ ccall("_esws_on_message",
+ "void",
+ ["number", "number", "number", "number"],
+ [c_ptr, out, len, is_string]
+ );
+ Module._free(out);
+ });
+
+ socket.addEventListener("error", function (event) {
+ if (!Module.IDHandler.has($0))
+ return; // Godot Object is gone!
+ ccall("_esws_on_error",
+ "void",
+ ["number"],
+ [c_ptr]
+ );
+ });
+
+ socket.addEventListener("close", function (event) {
+ if (!Module.IDHandler.has($0))
+ return; // Godot Object is gone!
+ var was_clean = 0;
+ if (event.was_clean)
+ was_clean = 1;
+ ccall("_esws_on_close",
+ "void",
+ ["number", "number", "string", "number"],
+ [c_ptr, event.code, event.reason, was_clean]
+ );
+ });
+
+ return Module.IDHandler.add(socket);
+ }, _js_id, str.utf8().get_data(), proto_string.utf8().get_data());
+ /* clang-format on */
+
+ static_cast<Ref<EMWSPeer> >(_peer)->set_sock(peer_sock);
+
+ return OK;
+};
+
+void EMWSClient::poll() {
+}
+
+Ref<WebSocketPeer> EMWSClient::get_peer(int p_peer_id) const {
+
+ return _peer;
+}
+
+NetworkedMultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const {
+
+ if (_peer->is_connected_to_host())
+ return CONNECTION_CONNECTED;
+
+ if (_is_connecting)
+ return CONNECTION_CONNECTING;
+
+ return CONNECTION_DISCONNECTED;
+};
+
+void EMWSClient::disconnect_from_host() {
+
+ _peer->close();
+};
+
+IP_Address EMWSClient::get_connected_host() const {
+
+ return IP_Address();
+};
+
+uint16_t EMWSClient::get_connected_port() const {
+
+ return 1025;
+};
+
+EMWSClient::EMWSClient() {
+ _is_connecting = false;
+ _peer = Ref<EMWSPeer>(memnew(EMWSPeer));
+ /* clang-format off */
+ _js_id = EM_ASM_INT({
+ return Module.IDHandler.add($0);
+ }, this);
+ /* clang-format on */
+};
+
+EMWSClient::~EMWSClient() {
+
+ disconnect_from_host();
+ _peer = Ref<EMWSPeer>();
+ /* clang-format off */
+ EM_ASM({
+ Module.IDHandler.remove($0);
+ }, _js_id);
+ /* clang-format on */
+};
+
+#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h
new file mode 100644
index 0000000000..8801f37007
--- /dev/null
+++ b/modules/websocket/emws_client.h
@@ -0,0 +1,62 @@
+/*************************************************************************/
+/* emws_client.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef EMWSCLIENT_H
+#define EMWSCLIENT_H
+
+#ifdef JAVASCRIPT_ENABLED
+
+#include "core/error_list.h"
+#include "emws_peer.h"
+#include "websocket_client.h"
+
+class EMWSClient : public WebSocketClient {
+
+ GDCIIMPL(EMWSClient, WebSocketClient);
+
+private:
+ int _js_id;
+
+public:
+ bool _is_connecting;
+
+ Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>());
+ Ref<WebSocketPeer> get_peer(int p_peer_id) const;
+ void disconnect_from_host();
+ IP_Address get_connected_host() const;
+ uint16_t get_connected_port() const;
+ virtual ConnectionStatus get_connection_status() const;
+ virtual void poll();
+ EMWSClient();
+ ~EMWSClient();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // EMWSCLIENT_H
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
new file mode 100644
index 0000000000..93665e6428
--- /dev/null
+++ b/modules/websocket/emws_peer.cpp
@@ -0,0 +1,173 @@
+/*************************************************************************/
+/* emws_peer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifdef JAVASCRIPT_ENABLED
+
+#include "emws_peer.h"
+#include "core/io/ip.h"
+
+void EMWSPeer::set_sock(int p_sock) {
+
+ peer_sock = p_sock;
+ in_buffer.clear();
+ queue_count = 0;
+}
+
+void EMWSPeer::set_write_mode(WriteMode p_mode) {
+ write_mode = p_mode;
+}
+
+EMWSPeer::WriteMode EMWSPeer::get_write_mode() const {
+ return write_mode;
+}
+
+void EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) {
+
+ if (in_buffer.space_left() < p_size + 5) {
+ ERR_EXPLAIN("Buffer full! Dropping data");
+ ERR_FAIL();
+ }
+
+ uint8_t is_string = p_is_string ? 1 : 0;
+ in_buffer.write((uint8_t *)&p_size, 4);
+ in_buffer.write((uint8_t *)&is_string, 1);
+ in_buffer.write(p_data, p_size);
+ queue_count++;
+}
+
+Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
+
+ int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0;
+
+ /* clang-format off */
+ EM_ASM({
+ var sock = Module.IDHandler.get($0);
+ var bytes_array = new Uint8Array($2);
+ var i = 0;
+
+ for(i=0; i<$2; i++) {
+ bytes_array[i] = getValue($1+i, 'i8');
+ }
+
+ if ($3) {
+ sock.send(bytes_array.buffer);
+ } else {
+ var string = new TextDecoder("utf-8").decode(bytes_array);
+ sock.send(string);
+ }
+ }, peer_sock, p_buffer, p_buffer_size, is_bin);
+ /* clang-format on */
+
+ return OK;
+};
+
+Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
+
+ if (queue_count == 0)
+ return ERR_UNAVAILABLE;
+
+ uint32_t to_read = 0;
+ uint32_t left = 0;
+ uint8_t is_string = 0;
+ r_buffer_size = 0;
+
+ in_buffer.read((uint8_t *)&to_read, 4);
+ --queue_count;
+ left = in_buffer.data_left();
+
+ if (left < to_read + 1) {
+ in_buffer.advance_read(left);
+ return FAILED;
+ }
+
+ in_buffer.read(&is_string, 1);
+ _was_string = is_string == 1;
+ in_buffer.read(packet_buffer, to_read);
+ *r_buffer = packet_buffer;
+ r_buffer_size = to_read;
+
+ return OK;
+};
+
+int EMWSPeer::get_available_packet_count() const {
+
+ return queue_count;
+};
+
+bool EMWSPeer::was_string_packet() const {
+
+ return _was_string;
+};
+
+bool EMWSPeer::is_connected_to_host() const {
+
+ return peer_sock != -1;
+};
+
+void EMWSPeer::close() {
+
+ if (peer_sock != -1) {
+ /* clang-format off */
+ EM_ASM({
+ var sock = Module.IDHandler.get($0);
+ sock.close();
+ Module.IDHandler.remove($0);
+ }, peer_sock);
+ /* clang-format on */
+ }
+ peer_sock = -1;
+ queue_count = 0;
+ in_buffer.clear();
+};
+
+IP_Address EMWSPeer::get_connected_host() const {
+
+ return IP_Address();
+};
+
+uint16_t EMWSPeer::get_connected_port() const {
+
+ return 1025;
+};
+
+EMWSPeer::EMWSPeer() {
+ peer_sock = -1;
+ queue_count = 0;
+ _was_string = false;
+ in_buffer.resize(16);
+ write_mode = WRITE_MODE_BINARY;
+};
+
+EMWSPeer::~EMWSPeer() {
+
+ in_buffer.resize(0);
+ close();
+};
+
+#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h
new file mode 100644
index 0000000000..a50d1874ba
--- /dev/null
+++ b/modules/websocket/emws_peer.h
@@ -0,0 +1,85 @@
+/*************************************************************************/
+/* emws_peer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef EMWSPEER_H
+#define EMWSPEER_H
+
+#ifdef JAVASCRIPT_ENABLED
+
+#include "core/error_list.h"
+#include "core/io/packet_peer.h"
+#include "core/ring_buffer.h"
+#include "emscripten.h"
+#include "websocket_peer.h"
+
+class EMWSPeer : public WebSocketPeer {
+
+ GDCIIMPL(EMWSPeer, WebSocketPeer);
+
+private:
+ enum {
+ PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for for type
+ };
+
+ int peer_sock;
+ WriteMode write_mode;
+
+ uint8_t packet_buffer[PACKET_BUFFER_SIZE];
+ RingBuffer<uint8_t> in_buffer;
+ int queue_count;
+ bool _was_string;
+
+public:
+ void read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string);
+ void set_sock(int sock);
+ virtual int get_available_packet_count() const;
+ virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size);
+ virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size);
+ virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; };
+
+ virtual void close();
+ virtual bool is_connected_to_host() const;
+ virtual IP_Address get_connected_host() const;
+ virtual uint16_t get_connected_port() const;
+
+ virtual WriteMode get_write_mode() const;
+ virtual void set_write_mode(WriteMode p_mode);
+ virtual bool was_string_packet() const;
+
+ void set_wsi(struct lws *wsi);
+ Error read_wsi(void *in, size_t len);
+ Error write_wsi();
+
+ EMWSPeer();
+ ~EMWSPeer();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // LSWPEER_H
diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp
new file mode 100644
index 0000000000..60e9133225
--- /dev/null
+++ b/modules/websocket/emws_server.cpp
@@ -0,0 +1,67 @@
+/*************************************************************************/
+/* emws_server.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifdef JAVASCRIPT_ENABLED
+
+#include "emws_server.h"
+#include "core/os/os.h"
+
+Error EMWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) {
+
+ return FAILED;
+}
+
+bool EMWSServer::is_listening() const {
+ return false;
+}
+
+void EMWSServer::stop() {
+}
+
+bool EMWSServer::has_peer(int p_id) const {
+ return false;
+}
+
+Ref<WebSocketPeer> EMWSServer::get_peer(int p_id) const {
+ return NULL;
+}
+
+PoolVector<String> EMWSServer::get_protocols() const {
+ PoolVector<String> out;
+
+ return out;
+}
+
+EMWSServer::EMWSServer() {
+}
+
+EMWSServer::~EMWSServer() {
+}
+
+#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h
new file mode 100644
index 0000000000..59f1d76346
--- /dev/null
+++ b/modules/websocket/emws_server.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* emws_server.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef EMWSSERVER_H
+#define EMWSSERVER_H
+
+#ifdef JAVASCRIPT_ENABLED
+
+#include "core/reference.h"
+#include "emws_peer.h"
+#include "websocket_server.h"
+
+class EMWSServer : public WebSocketServer {
+
+ GDCIIMPL(EMWSServer, WebSocketServer);
+
+public:
+ Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false);
+ void stop();
+ bool is_listening() const;
+ bool has_peer(int p_id) const;
+ Ref<WebSocketPeer> get_peer(int p_id) const;
+ virtual void poll();
+ virtual PoolVector<String> get_protocols() const;
+
+ EMWSServer();
+ ~EMWSServer();
+};
+
+#endif
+
+#endif // LWSSERVER_H
diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp
new file mode 100644
index 0000000000..604b1886ad
--- /dev/null
+++ b/modules/websocket/lws_client.cpp
@@ -0,0 +1,203 @@
+/*************************************************************************/
+/* lws_client.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef JAVASCRIPT_ENABLED
+
+#include "lws_client.h"
+#include "core/io/ip.h"
+
+Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) {
+
+ ERR_FAIL_COND_V(context != NULL, FAILED);
+
+ IP_Address addr;
+
+ if (!p_host.is_valid_ip_address()) {
+ addr = IP::get_singleton()->resolve_hostname(p_host);
+ } else {
+ addr = p_host;
+ }
+
+ ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER);
+
+ // prepare protocols
+ if (p_protocols.size() == 0) // default to binary protocol
+ p_protocols.append("binary");
+ _lws_make_protocols(this, &LWSClient::_lws_gd_callback, p_protocols, &_lws_ref);
+
+ // init lws client
+ struct lws_context_creation_info info;
+ struct lws_client_connect_info i;
+
+ memset(&i, 0, sizeof i);
+ memset(&info, 0, sizeof info);
+
+ info.port = CONTEXT_PORT_NO_LISTEN;
+ info.protocols = _lws_ref->lws_structs;
+ info.gid = -1;
+ info.uid = -1;
+ //info.ws_ping_pong_interval = 5;
+ info.user = _lws_ref;
+ context = lws_create_context(&info);
+
+ if (context == NULL) {
+ _lws_free_ref(_lws_ref);
+ _lws_ref = NULL;
+ ERR_EXPLAIN("Unable to create lws context");
+ ERR_FAIL_V(FAILED);
+ }
+
+ char abuf[1024];
+ char hbuf[1024];
+ char pbuf[2048];
+ String addr_str = (String)addr;
+ strncpy(abuf, addr_str.ascii().get_data(), 1024);
+ strncpy(hbuf, p_host.utf8().get_data(), 1024);
+ strncpy(pbuf, p_path.utf8().get_data(), 2048);
+
+ i.context = context;
+ i.protocol = _lws_ref->lws_names;
+ i.address = abuf;
+ i.host = hbuf;
+ i.path = pbuf;
+ i.port = p_port;
+ i.ssl_connection = p_ssl;
+
+ lws_client_connect_via_info(&i);
+ return OK;
+};
+
+void LWSClient::poll() {
+
+ _lws_poll();
+}
+
+int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
+
+ Ref<LWSPeer> peer = static_cast<Ref<LWSPeer> >(_peer);
+ LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user;
+
+ switch (reason) {
+
+ case LWS_CALLBACK_CLIENT_ESTABLISHED:
+ peer->set_wsi(wsi);
+ peer_data->peer_id = 0;
+ peer_data->in_size = 0;
+ peer_data->in_count = 0;
+ peer_data->out_count = 0;
+ peer_data->rbw.resize(16);
+ peer_data->rbr.resize(16);
+ peer_data->force_close = false;
+ _on_connect(lws_get_protocol(wsi)->name);
+ break;
+
+ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+ _on_error();
+ destroy_context();
+ return -1; // we should close the connection (would probably happen anyway)
+
+ case LWS_CALLBACK_CLOSED:
+ peer_data->in_count = 0;
+ peer_data->out_count = 0;
+ peer_data->rbw.resize(0);
+ peer_data->rbr.resize(0);
+ peer->close();
+ destroy_context();
+ _on_disconnect();
+ return 0; // we can end here
+
+ case LWS_CALLBACK_CLIENT_RECEIVE:
+ peer->read_wsi(in, len);
+ if (peer->get_available_packet_count() > 0)
+ _on_peer_packet();
+ break;
+
+ case LWS_CALLBACK_CLIENT_WRITEABLE:
+ if (peer_data->force_close)
+ return -1;
+
+ peer->write_wsi();
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+Ref<WebSocketPeer> LWSClient::get_peer(int p_peer_id) const {
+
+ return _peer;
+}
+
+NetworkedMultiplayerPeer::ConnectionStatus LWSClient::get_connection_status() const {
+
+ if (context == NULL)
+ return CONNECTION_DISCONNECTED;
+
+ if (_peer->is_connected_to_host())
+ return CONNECTION_CONNECTED;
+
+ return CONNECTION_CONNECTING;
+}
+
+void LWSClient::disconnect_from_host() {
+
+ if (context == NULL)
+ return;
+
+ _peer->close();
+ destroy_context();
+};
+
+IP_Address LWSClient::get_connected_host() const {
+
+ return IP_Address();
+};
+
+uint16_t LWSClient::get_connected_port() const {
+
+ return 1025;
+};
+
+LWSClient::LWSClient() {
+ context = NULL;
+ _lws_ref = NULL;
+ _peer = Ref<LWSPeer>(memnew(LWSPeer));
+};
+
+LWSClient::~LWSClient() {
+
+ invalidate_lws_ref(); // We do not want any more callback
+ disconnect_from_host();
+ _peer = Ref<LWSPeer>();
+};
+
+#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/lws_client.h b/modules/websocket/lws_client.h
new file mode 100644
index 0000000000..2e082175df
--- /dev/null
+++ b/modules/websocket/lws_client.h
@@ -0,0 +1,61 @@
+/*************************************************************************/
+/* lws_client.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef LWSCLIENT_H
+#define LWSCLIENT_H
+
+#ifndef JAVASCRIPT_ENABLED
+
+#include "core/error_list.h"
+#include "lws_helper.h"
+#include "lws_peer.h"
+#include "websocket_client.h"
+
+class LWSClient : public WebSocketClient {
+
+ GDCIIMPL(LWSClient, WebSocketClient);
+
+ LWS_HELPER(LWSClient);
+
+public:
+ Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>());
+ Ref<WebSocketPeer> get_peer(int p_peer_id) const;
+ void disconnect_from_host();
+ IP_Address get_connected_host() const;
+ uint16_t get_connected_port() const;
+ virtual ConnectionStatus get_connection_status() const;
+ virtual void poll();
+
+ LWSClient();
+ ~LWSClient();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // LWSCLIENT_H
diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h
new file mode 100644
index 0000000000..ac0c340aa9
--- /dev/null
+++ b/modules/websocket/lws_helper.h
@@ -0,0 +1,214 @@
+/*************************************************************************/
+/* lws_helper.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef LWS_HELPER_H
+#define LWS_HELPER_H
+
+#include "core/io/stream_peer.h"
+#include "core/os/os.h"
+#include "core/reference.h"
+#include "core/ring_buffer.h"
+#include "lws_peer.h"
+
+struct _LWSRef {
+ bool free_context;
+ bool is_polling;
+ bool is_valid;
+ bool is_destroying;
+ void *obj;
+ struct lws_protocols *lws_structs;
+ char *lws_names;
+};
+
+static _LWSRef *_lws_create_ref(void *obj) {
+
+ _LWSRef *out = (_LWSRef *)memalloc(sizeof(_LWSRef));
+ out->is_destroying = false;
+ out->free_context = false;
+ out->is_polling = false;
+ out->obj = obj;
+ out->is_valid = true;
+ out->lws_structs = NULL;
+ out->lws_names = NULL;
+ return out;
+}
+
+static void _lws_free_ref(_LWSRef *ref) {
+ // Free strings and structs
+ memfree(ref->lws_structs);
+ memfree(ref->lws_names);
+ // Free ref
+ memfree(ref);
+}
+
+static bool _lws_destroy(struct lws_context *context, _LWSRef *ref) {
+ if (context == NULL || ref->is_destroying)
+ return false;
+
+ if (ref->is_polling) {
+ ref->free_context = true;
+ return false;
+ }
+
+ ref->is_destroying = true;
+ lws_context_destroy(context);
+ _lws_free_ref(ref);
+ return true;
+}
+
+static bool _lws_poll(struct lws_context *context, _LWSRef *ref) {
+
+ ERR_FAIL_COND_V(context == NULL, false);
+ ERR_FAIL_COND_V(ref == NULL, false);
+
+ ref->is_polling = true;
+ lws_service(context, 0);
+ ref->is_polling = false;
+
+ if (!ref->free_context)
+ return false; // Nothing to do
+
+ bool is_valid = ref->is_valid; // Might have been destroyed by poll
+
+ _lws_destroy(context, ref); // Will destroy context and ref
+
+ return is_valid; // If the object should NULL its context and ref
+}
+
+/*
+ * prepare the protocol_structs to be fed to context
+ * also prepare the protocol string used by the client
+ */
+static void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref) {
+ /* the input strings might go away after this call,
+ * we need to copy them. Will clear them when
+ * detroying the context */
+ int i;
+ int len = p_names.size();
+ size_t data_size = sizeof(struct LWSPeer::PeerData);
+ PoolVector<String>::Read pnr = p_names.read();
+
+ /*
+ * This is a reference connecting the object with lws
+ * keep track of status, mallocs, etc.
+ * Must survive as long the context
+ * Must be freed manually when context creation fails.
+ */
+ _LWSRef *ref = _lws_create_ref(p_obj);
+
+ /* LWS protocol structs */
+ ref->lws_structs = (struct lws_protocols *)memalloc(sizeof(struct lws_protocols) * (len + 2));
+
+ CharString strings = p_names.join(",").ascii();
+ int str_len = strings.length();
+
+ /* Joined string of protocols, double the size: comma separated first, NULL separated last */
+ ref->lws_names = (char *)memalloc((str_len + 1) * 2); /* plus the terminator */
+
+ char *names_ptr = ref->lws_names;
+ struct lws_protocols *structs_ptr = ref->lws_structs;
+
+ copymem(names_ptr, strings.get_data(), str_len);
+ names_ptr[str_len] = '\0'; /* NULL terminator */
+ /* NULL terminated strings to be used in protocol structs */
+ copymem(&names_ptr[str_len + 1], strings.get_data(), str_len);
+ names_ptr[(str_len * 2) + 1] = '\0'; /* NULL terminator */
+ int pos = str_len + 1;
+
+ /* the first protocol is always http-only */
+ structs_ptr[0].name = "http-only";
+ structs_ptr[0].callback = p_callback;
+ structs_ptr[0].per_session_data_size = data_size;
+ structs_ptr[0].rx_buffer_size = 0;
+ /* add user defined protocols */
+ for (i = 0; i < len; i++) {
+ structs_ptr[i + 1].name = (const char *)&names_ptr[pos];
+ structs_ptr[i + 1].callback = p_callback;
+ structs_ptr[i + 1].per_session_data_size = data_size;
+ structs_ptr[i + 1].rx_buffer_size = 0;
+ pos += pnr[i].ascii().length() + 1;
+ names_ptr[pos - 1] = '\0';
+ }
+ /* add protocols terminator */
+ structs_ptr[len + 1].name = NULL;
+ structs_ptr[len + 1].callback = NULL;
+ structs_ptr[len + 1].per_session_data_size = 0;
+ structs_ptr[len + 1].rx_buffer_size = 0;
+
+ *r_lws_ref = ref;
+}
+
+/* clang-format off */
+#define LWS_HELPER(CNAME) \
+protected: \
+ struct _LWSRef *_lws_ref; \
+ struct lws_context *context; \
+ \
+ static int _lws_gd_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { \
+ \
+ if (wsi == NULL) { \
+ return 0; \
+ } \
+ \
+ struct _LWSRef *ref = (struct _LWSRef *)lws_context_user(lws_get_context(wsi)); \
+ if (!ref->is_valid) \
+ return 0; \
+ CNAME *helper = (CNAME *)ref->obj; \
+ return helper->_handle_cb(wsi, reason, user, in, len); \
+ } \
+ \
+ void invalidate_lws_ref() { \
+ if (_lws_ref != NULL) \
+ _lws_ref->is_valid = false; \
+ } \
+ \
+ void destroy_context() { \
+ if (_lws_destroy(context, _lws_ref)) { \
+ context = NULL; \
+ _lws_ref = NULL; \
+ } \
+ } \
+ \
+public: \
+ virtual int _handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); \
+ \
+ void _lws_poll() { \
+ ERR_FAIL_COND(context == NULL); \
+ \
+ if (::_lws_poll(context, _lws_ref)) { \
+ context = NULL; \
+ _lws_ref = NULL; \
+ } \
+ } \
+ \
+protected:
+
+ /* clang-format on */
+
+#endif // LWS_HELPER_H
diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp
new file mode 100644
index 0000000000..fdaa79f9d4
--- /dev/null
+++ b/modules/websocket/lws_peer.cpp
@@ -0,0 +1,200 @@
+/*************************************************************************/
+/* lws_peer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef JAVASCRIPT_ENABLED
+
+#include "lws_peer.h"
+#include "core/io/ip.h"
+
+void LWSPeer::set_wsi(struct lws *p_wsi) {
+ wsi = p_wsi;
+};
+
+void LWSPeer::set_write_mode(WriteMode p_mode) {
+ write_mode = p_mode;
+}
+
+LWSPeer::WriteMode LWSPeer::get_write_mode() const {
+ return write_mode;
+}
+
+Error LWSPeer::read_wsi(void *in, size_t len) {
+
+ ERR_FAIL_COND_V(!is_connected_to_host(), FAILED);
+
+ PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi));
+ uint32_t size = peer_data->in_size;
+ uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1;
+
+ if (peer_data->rbr.space_left() < len + 5) {
+ ERR_EXPLAIN("Buffer full! Dropping data");
+ ERR_FAIL_V(FAILED);
+ }
+
+ copymem(&(peer_data->input_buffer[size]), in, len);
+ size += len;
+
+ peer_data->in_size = size;
+ if (lws_is_final_fragment(wsi)) {
+ peer_data->rbr.write((uint8_t *)&size, 4);
+ peer_data->rbr.write((uint8_t *)&is_string, 1);
+ peer_data->rbr.write(peer_data->input_buffer, size);
+ peer_data->in_count++;
+ peer_data->in_size = 0;
+ }
+
+ return OK;
+}
+
+Error LWSPeer::write_wsi() {
+
+ ERR_FAIL_COND_V(!is_connected_to_host(), FAILED);
+
+ PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi));
+ PoolVector<uint8_t> tmp;
+ int left = peer_data->rbw.data_left();
+ uint32_t to_write = 0;
+
+ if (left == 0 || peer_data->out_count == 0)
+ return OK;
+
+ peer_data->rbw.read((uint8_t *)&to_write, 4);
+ peer_data->out_count--;
+
+ if (left < to_write) {
+ peer_data->rbw.advance_read(left);
+ return FAILED;
+ }
+
+ tmp.resize(LWS_PRE + to_write);
+ peer_data->rbw.read(&(tmp.write()[LWS_PRE]), to_write);
+ lws_write(wsi, &(tmp.write()[LWS_PRE]), to_write, (enum lws_write_protocol)write_mode);
+ tmp.resize(0);
+
+ if (peer_data->out_count > 0)
+ lws_callback_on_writable(wsi); // we want to write more!
+
+ return OK;
+}
+
+Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
+
+ ERR_FAIL_COND_V(!is_connected_to_host(), FAILED);
+
+ PeerData *peer_data = (PeerData *)lws_wsi_user(wsi);
+ peer_data->rbw.write((uint8_t *)&p_buffer_size, 4);
+ peer_data->rbw.write(p_buffer, MIN(p_buffer_size, peer_data->rbw.space_left()));
+ peer_data->out_count++;
+
+ lws_callback_on_writable(wsi); // notify that we want to write
+ return OK;
+};
+
+Error LWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
+
+ ERR_FAIL_COND_V(!is_connected_to_host(), FAILED);
+
+ PeerData *peer_data = (PeerData *)lws_wsi_user(wsi);
+
+ if (peer_data->in_count == 0)
+ return ERR_UNAVAILABLE;
+
+ uint32_t to_read = 0;
+ uint32_t left = 0;
+ uint8_t is_string = 0;
+ r_buffer_size = 0;
+
+ peer_data->rbr.read((uint8_t *)&to_read, 4);
+ peer_data->in_count--;
+ left = peer_data->rbr.data_left();
+
+ if (left < to_read + 1) {
+ peer_data->rbr.advance_read(left);
+ return FAILED;
+ }
+
+ peer_data->rbr.read(&is_string, 1);
+ peer_data->rbr.read(packet_buffer, to_read);
+ *r_buffer = packet_buffer;
+ r_buffer_size = to_read;
+ _was_string = is_string;
+
+ return OK;
+};
+
+int LWSPeer::get_available_packet_count() const {
+
+ if (!is_connected_to_host())
+ return 0;
+
+ return ((PeerData *)lws_wsi_user(wsi))->in_count;
+};
+
+bool LWSPeer::was_string_packet() const {
+
+ return _was_string;
+};
+
+bool LWSPeer::is_connected_to_host() const {
+
+ return wsi != NULL;
+};
+
+void LWSPeer::close() {
+ if (wsi != NULL) {
+ struct lws *tmp = wsi;
+ PeerData *data = ((PeerData *)lws_wsi_user(wsi));
+ data->force_close = true;
+ wsi = NULL;
+ lws_callback_on_writable(tmp); // notify that we want to disconnect
+ }
+};
+
+IP_Address LWSPeer::get_connected_host() const {
+
+ return IP_Address();
+};
+
+uint16_t LWSPeer::get_connected_port() const {
+
+ return 1025;
+};
+
+LWSPeer::LWSPeer() {
+ wsi = NULL;
+ _was_string = false;
+ write_mode = WRITE_MODE_BINARY;
+};
+
+LWSPeer::~LWSPeer() {
+
+ close();
+};
+
+#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/lws_peer.h b/modules/websocket/lws_peer.h
new file mode 100644
index 0000000000..0a62b65d24
--- /dev/null
+++ b/modules/websocket/lws_peer.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* lws_peer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef LWSPEER_H
+#define LWSPEER_H
+
+#ifndef JAVASCRIPT_ENABLED
+
+#include "core/error_list.h"
+#include "core/io/packet_peer.h"
+#include "core/ring_buffer.h"
+#include "libwebsockets.h"
+#include "lws_config.h"
+#include "websocket_peer.h"
+
+class LWSPeer : public WebSocketPeer {
+
+ GDCIIMPL(LWSPeer, WebSocketPeer);
+
+private:
+ enum {
+ PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for the type
+ };
+
+ uint8_t packet_buffer[PACKET_BUFFER_SIZE];
+ struct lws *wsi;
+ WriteMode write_mode;
+ bool _was_string;
+
+public:
+ struct PeerData {
+ uint32_t peer_id;
+ bool force_close;
+ RingBuffer<uint8_t> rbw;
+ RingBuffer<uint8_t> rbr;
+ mutable uint8_t input_buffer[PACKET_BUFFER_SIZE];
+ uint32_t in_size;
+ int in_count;
+ int out_count;
+ };
+
+ virtual int get_available_packet_count() const;
+ virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size);
+ virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size);
+ virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; };
+
+ virtual void close();
+ virtual bool is_connected_to_host() const;
+ virtual IP_Address get_connected_host() const;
+ virtual uint16_t get_connected_port() const;
+
+ virtual WriteMode get_write_mode() const;
+ virtual void set_write_mode(WriteMode p_mode);
+ virtual bool was_string_packet() const;
+
+ void set_wsi(struct lws *wsi);
+ Error read_wsi(void *in, size_t len);
+ Error write_wsi();
+
+ LWSPeer();
+ ~LWSPeer();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // LSWPEER_H
diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp
new file mode 100644
index 0000000000..8a47ba557d
--- /dev/null
+++ b/modules/websocket/lws_server.cpp
@@ -0,0 +1,177 @@
+/*************************************************************************/
+/* lws_server.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef JAVASCRIPT_ENABLED
+
+#include "lws_server.h"
+#include "core/os/os.h"
+
+Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) {
+
+ ERR_FAIL_COND_V(context != NULL, FAILED);
+
+ _is_multiplayer = gd_mp_api;
+
+ struct lws_context_creation_info info;
+ memset(&info, 0, sizeof info);
+
+ if (p_protocols.size() == 0) // default to binary protocol
+ p_protocols.append(String("binary"));
+
+ // Prepare lws protocol structs
+ _lws_make_protocols(this, &LWSServer::_lws_gd_callback, p_protocols, &_lws_ref);
+
+ info.port = p_port;
+ info.user = _lws_ref;
+ info.protocols = _lws_ref->lws_structs;
+ info.gid = -1;
+ info.uid = -1;
+ //info.ws_ping_pong_interval = 5;
+
+ context = lws_create_context(&info);
+
+ if (context == NULL) {
+ _lws_free_ref(_lws_ref);
+ _lws_ref = NULL;
+ ERR_EXPLAIN("Unable to create LWS context");
+ ERR_FAIL_V(FAILED);
+ }
+
+ return OK;
+}
+
+bool LWSServer::is_listening() const {
+ return context != NULL;
+}
+
+int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
+
+ LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user;
+
+ switch (reason) {
+ case LWS_CALLBACK_HTTP:
+ // no http for now
+ // closing immediately returning -1;
+ return -1;
+
+ case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
+ // check header here?
+ break;
+
+ case LWS_CALLBACK_ESTABLISHED: {
+ int32_t id = _gen_unique_id();
+
+ Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer));
+ peer->set_wsi(wsi);
+ _peer_map[id] = peer;
+
+ peer_data->peer_id = id;
+ peer_data->in_size = 0;
+ peer_data->in_count = 0;
+ peer_data->out_count = 0;
+ peer_data->rbw.resize(16);
+ peer_data->rbr.resize(16);
+ peer_data->force_close = false;
+
+ _on_connect(id, lws_get_protocol(wsi)->name);
+ break;
+ }
+
+ case LWS_CALLBACK_CLOSED: {
+ if (peer_data == NULL)
+ return 0;
+ int32_t id = peer_data->peer_id;
+ if (_peer_map.has(id)) {
+ _peer_map[id]->close();
+ _peer_map.erase(id);
+ }
+ peer_data->in_count = 0;
+ peer_data->out_count = 0;
+ peer_data->rbr.resize(0);
+ peer_data->rbw.resize(0);
+ _on_disconnect(id);
+ return 0; // we can end here
+ }
+
+ case LWS_CALLBACK_RECEIVE: {
+ int32_t id = peer_data->peer_id;
+ if (_peer_map.has(id)) {
+ static_cast<Ref<LWSPeer> >(_peer_map[id])->read_wsi(in, len);
+ if (_peer_map[id]->get_available_packet_count() > 0)
+ _on_peer_packet(id);
+ }
+ break;
+ }
+
+ case LWS_CALLBACK_SERVER_WRITEABLE: {
+ if (peer_data->force_close)
+ return -1;
+
+ int id = peer_data->peer_id;
+ if (_peer_map.has(id))
+ static_cast<Ref<LWSPeer> >(_peer_map[id])->write_wsi();
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void LWSServer::stop() {
+ if (context == NULL)
+ return;
+
+ _peer_map.clear();
+ destroy_context();
+ context = NULL;
+}
+
+bool LWSServer::has_peer(int p_id) const {
+ return _peer_map.has(p_id);
+}
+
+Ref<WebSocketPeer> LWSServer::get_peer(int p_id) const {
+ ERR_FAIL_COND_V(!has_peer(p_id), NULL);
+ return _peer_map[p_id];
+}
+
+LWSServer::LWSServer() {
+ context = NULL;
+ _lws_ref = NULL;
+}
+
+LWSServer::~LWSServer() {
+ invalidate_lws_ref(); // we do not want any more callbacks
+ stop();
+}
+
+#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/lws_server.h b/modules/websocket/lws_server.h
new file mode 100644
index 0000000000..5f7ac4850a
--- /dev/null
+++ b/modules/websocket/lws_server.h
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* lws_server.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef LWSSERVER_H
+#define LWSSERVER_H
+
+#ifndef JAVASCRIPT_ENABLED
+
+#include "core/reference.h"
+#include "lws_helper.h"
+#include "lws_peer.h"
+#include "websocket_server.h"
+
+class LWSServer : public WebSocketServer {
+
+ GDCIIMPL(LWSServer, WebSocketServer);
+
+ LWS_HELPER(LWSServer);
+
+private:
+ Map<int, Ref<LWSPeer> > peer_map;
+
+public:
+ Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false);
+ void stop();
+ bool is_listening() const;
+ bool has_peer(int p_id) const;
+ Ref<WebSocketPeer> get_peer(int p_id) const;
+ virtual void poll() { _lws_poll(); }
+
+ LWSServer();
+ ~LWSServer();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // LWSSERVER_H
diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp
new file mode 100644
index 0000000000..39d03ff1f0
--- /dev/null
+++ b/modules/websocket/register_types.cpp
@@ -0,0 +1,79 @@
+/*************************************************************************/
+/* register_types.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "register_types.h"
+#include "error_macros.h"
+#ifdef JAVASCRIPT_ENABLED
+#include "emscripten.h"
+#include "emws_client.h"
+#include "emws_peer.h"
+#include "emws_server.h"
+#else
+#include "lws_client.h"
+#include "lws_peer.h"
+#include "lws_server.h"
+#endif
+
+void register_websocket_types() {
+#ifdef JAVASCRIPT_ENABLED
+ EM_ASM({
+ var IDHandler = {};
+ IDHandler["ids"] = {};
+ IDHandler["has"] = function(id) {
+ return IDHandler.ids.hasOwnProperty(id);
+ };
+ IDHandler["add"] = function(obj) {
+ var id = crypto.getRandomValues(new Int32Array(32))[0];
+ IDHandler.ids[id] = obj;
+ return id;
+ };
+ IDHandler["get"] = function(id) {
+ return IDHandler.ids[id];
+ };
+ IDHandler["remove"] = function(id) {
+ delete IDHandler.ids[id];
+ };
+ Module["IDHandler"] = IDHandler;
+ });
+ EMWSPeer::make_default();
+ EMWSClient::make_default();
+ EMWSServer::make_default();
+#else
+ LWSPeer::make_default();
+ LWSClient::make_default();
+ LWSServer::make_default();
+#endif
+
+ ClassDB::register_virtual_class<WebSocketMultiplayerPeer>();
+ ClassDB::register_custom_instance_class<WebSocketServer>();
+ ClassDB::register_custom_instance_class<WebSocketClient>();
+ ClassDB::register_custom_instance_class<WebSocketPeer>();
+}
+
+void unregister_websocket_types() {}
diff --git a/modules/websocket/register_types.h b/modules/websocket/register_types.h
new file mode 100644
index 0000000000..010d88789b
--- /dev/null
+++ b/modules/websocket/register_types.h
@@ -0,0 +1,31 @@
+/*************************************************************************/
+/* register_types.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+void register_websocket_types();
+void unregister_websocket_types();
diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp
new file mode 100644
index 0000000000..f92a386988
--- /dev/null
+++ b/modules/websocket/websocket_client.cpp
@@ -0,0 +1,124 @@
+/*************************************************************************/
+/* websocket_client.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "websocket_client.h"
+
+GDCINULL(WebSocketClient);
+
+WebSocketClient::WebSocketClient() {
+}
+
+WebSocketClient::~WebSocketClient() {
+}
+
+Error WebSocketClient::connect_to_url(String p_url, PoolVector<String> p_protocols, bool gd_mp_api) {
+ _is_multiplayer = gd_mp_api;
+
+ String host = p_url;
+ String path = "/";
+ int p_len = -1;
+ int port = 80;
+ bool ssl = false;
+ if (host.begins_with("wss://")) {
+ ssl = true; // we should implement this
+ host = host.substr(6, host.length() - 6);
+ port = 443;
+ } else {
+ ssl = false;
+ if (host.begins_with("ws://"))
+ host = host.substr(5, host.length() - 5);
+ }
+
+ // Path
+ p_len = host.find("/");
+ if (p_len != -1) {
+ path = host.substr(p_len, host.length() - p_len);
+ host = host.substr(0, p_len);
+ }
+
+ // Port
+ p_len = host.find_last(":");
+ if (p_len != -1 && p_len == host.find(":")) {
+ port = host.substr(p_len, host.length() - p_len).to_int();
+ host = host.substr(0, p_len);
+ }
+
+ return connect_to_host(host, path, port, ssl, p_protocols);
+}
+
+bool WebSocketClient::is_server() const {
+
+ return false;
+}
+
+void WebSocketClient::_on_peer_packet() {
+
+ if (_is_multiplayer) {
+ _process_multiplayer(get_peer(1), 1);
+ } else {
+ emit_signal("data_received");
+ }
+}
+
+void WebSocketClient::_on_connect(String p_protocol) {
+
+ if (_is_multiplayer) {
+ // need to wait for ID confirmation...
+ } else {
+ emit_signal("connection_established", p_protocol);
+ }
+}
+
+void WebSocketClient::_on_disconnect() {
+
+ if (_is_multiplayer) {
+ emit_signal("connection_failed");
+ } else {
+ emit_signal("connection_closed");
+ }
+}
+
+void WebSocketClient::_on_error() {
+
+ if (_is_multiplayer) {
+ emit_signal("connection_failed");
+ } else {
+ emit_signal("connection_error");
+ }
+}
+
+void WebSocketClient::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api"), &WebSocketClient::connect_to_url, DEFVAL(PoolVector<String>()), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("disconnect_from_host"), &WebSocketClient::disconnect_from_host);
+
+ ADD_SIGNAL(MethodInfo("data_received"));
+ ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol")));
+ ADD_SIGNAL(MethodInfo("connection_closed"));
+ ADD_SIGNAL(MethodInfo("connection_error"));
+}
diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h
new file mode 100644
index 0000000000..0e87825222
--- /dev/null
+++ b/modules/websocket/websocket_client.h
@@ -0,0 +1,68 @@
+/*************************************************************************/
+/* websocket_client.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef WEBSOCKET_CLIENT_H
+#define WEBSOCKET_CLIENT_H
+
+#include "core/error_list.h"
+#include "websocket_multiplayer.h"
+#include "websocket_peer.h"
+
+class WebSocketClient : public WebSocketMultiplayerPeer {
+
+ GDCLASS(WebSocketClient, WebSocketMultiplayerPeer);
+ GDCICLASS(WebSocketClient);
+
+protected:
+ Ref<WebSocketPeer> _peer;
+
+ static void _bind_methods();
+
+public:
+ Error connect_to_url(String p_url, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false);
+
+ virtual void poll() = 0;
+ virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()) = 0;
+ virtual void disconnect_from_host() = 0;
+ virtual IP_Address get_connected_host() const = 0;
+ virtual uint16_t get_connected_port() const = 0;
+
+ virtual bool is_server() const;
+ virtual ConnectionStatus get_connection_status() const = 0;
+
+ void _on_peer_packet();
+ void _on_connect(String p_protocol);
+ void _on_disconnect();
+ void _on_error();
+
+ WebSocketClient();
+ ~WebSocketClient();
+};
+
+#endif // WEBSOCKET_CLIENT_H
diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h
new file mode 100644
index 0000000000..b5c2159806
--- /dev/null
+++ b/modules/websocket/websocket_macros.h
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* websocket_macros.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef WEBSOCKETMACTOS_H
+#define WEBSOCKETMACTOS_H
+
+/* clang-format off */
+#define GDCICLASS(CNAME) \
+public:\
+ static CNAME *(*_create)();\
+\
+ static Ref<CNAME > create_ref() {\
+\
+ if (!_create)\
+ return Ref<CNAME >();\
+ return Ref<CNAME >(_create());\
+ }\
+\
+ static CNAME *create() {\
+\
+ if (!_create)\
+ return NULL;\
+ return _create();\
+ }\
+protected:\
+
+#define GDCINULL(CNAME) \
+CNAME *(*CNAME::_create)() = NULL;
+
+#define GDCIIMPL(IMPNAME, CNAME) \
+public:\
+ static CNAME *_create() { return memnew(IMPNAME); }\
+ static void make_default() { CNAME::_create = IMPNAME::_create; }\
+protected:\
+/* clang-format on */
+
+#endif // WEBSOCKETMACTOS_H
diff --git a/modules/websocket/websocket_multiplayer.cpp b/modules/websocket/websocket_multiplayer.cpp
new file mode 100644
index 0000000000..8cd4dff38b
--- /dev/null
+++ b/modules/websocket/websocket_multiplayer.cpp
@@ -0,0 +1,361 @@
+/*************************************************************************/
+/* websocket_multiplayer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "websocket_multiplayer.h"
+#include "core/os/os.h"
+
+WebSocketMultiplayerPeer::WebSocketMultiplayerPeer() {
+
+ _is_multiplayer = false;
+ _peer_id = 0;
+ _target_peer = 0;
+ _refusing = false;
+
+ _current_packet.source = 0;
+ _current_packet.destination = 0;
+ _current_packet.size = 0;
+ _current_packet.data = NULL;
+}
+
+WebSocketMultiplayerPeer::~WebSocketMultiplayerPeer() {
+
+ _clear();
+}
+
+int WebSocketMultiplayerPeer::_gen_unique_id() const {
+
+ uint32_t hash = 0;
+
+ while (hash == 0 || hash == 1) {
+
+ hash = hash_djb2_one_32(
+ (uint32_t)OS::get_singleton()->get_ticks_usec());
+ hash = hash_djb2_one_32(
+ (uint32_t)OS::get_singleton()->get_unix_time(), hash);
+ hash = hash_djb2_one_32(
+ (uint32_t)OS::get_singleton()->get_data_path().hash64(), hash);
+ hash = hash_djb2_one_32(
+ (uint32_t)((uint64_t)this), hash); //rely on aslr heap
+ hash = hash_djb2_one_32(
+ (uint32_t)((uint64_t)&hash), hash); //rely on aslr stack
+ hash = hash & 0x7FFFFFFF; // make it compatible with unsigned, since negatie id is used for exclusion
+ }
+
+ return hash;
+}
+void WebSocketMultiplayerPeer::_clear() {
+
+ _peer_map.clear();
+ if (_current_packet.data != NULL)
+ memfree(_current_packet.data);
+
+ for (List<Packet>::Element *E = _incoming_packets.front(); E; E = E->next()) {
+ memfree(E->get().data);
+ E->get().data = NULL;
+ }
+
+ _incoming_packets.clear();
+}
+
+void WebSocketMultiplayerPeer::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer);
+
+ ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "peer_source")));
+}
+
+//
+// PacketPeer
+//
+int WebSocketMultiplayerPeer::get_available_packet_count() const {
+
+ ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED);
+
+ return _incoming_packets.size();
+}
+
+int WebSocketMultiplayerPeer::get_max_packet_size() const {
+
+ ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED);
+
+ return MAX_PACKET_SIZE;
+}
+
+Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
+
+ r_buffer_size = 0;
+ ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED);
+
+ if (_current_packet.data != NULL) {
+ memfree(_current_packet.data);
+ _current_packet.data = NULL;
+ }
+
+ _current_packet = _incoming_packets.front()->get();
+ _incoming_packets.pop_front();
+
+ *r_buffer = _current_packet.data;
+ r_buffer_size = _current_packet.size;
+
+ return OK;
+}
+
+Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
+
+ ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED);
+
+ PoolVector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size);
+
+ if (is_server()) {
+ return _server_relay(1, _target_peer, &(buffer.read()[0]), buffer.size());
+ } else {
+ return get_peer(1)->put_packet(&(buffer.read()[0]), buffer.size());
+ }
+}
+
+//
+// NetworkedMultiplayerPeer
+//
+void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) {
+
+ // Websocket uses TCP, reliable
+}
+
+NetworkedMultiplayerPeer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const {
+
+ // Websocket uses TCP, reliable
+ return TRANSFER_MODE_RELIABLE;
+}
+
+void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) {
+
+ _target_peer = p_target_peer;
+}
+
+int WebSocketMultiplayerPeer::get_packet_peer() const {
+
+ ERR_FAIL_COND_V(!_is_multiplayer, 1);
+ ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1);
+
+ return _incoming_packets.front()->get().source;
+}
+
+int WebSocketMultiplayerPeer::get_unique_id() const {
+
+ return _peer_id;
+}
+
+void WebSocketMultiplayerPeer::set_refuse_new_connections(bool p_enable) {
+
+ _refusing = p_enable;
+}
+
+bool WebSocketMultiplayerPeer::is_refusing_new_connections() const {
+
+ return _refusing;
+}
+
+void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id) {
+
+ ERR_FAIL_COND(!p_peer.is_valid());
+ ERR_FAIL_COND(!p_peer->is_connected_to_host());
+
+ PoolVector<uint8_t> message = _make_pkt(p_type, 1, 0, (uint8_t *)&p_peer_id, 4);
+ p_peer->put_packet(&(message.read()[0]), message.size());
+}
+
+PoolVector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) {
+
+ PoolVector<uint8_t> out;
+ out.resize(PROTO_SIZE + p_data_size);
+
+ PoolVector<uint8_t>::Write w = out.write();
+ copymem(&w[0], &p_type, 1);
+ copymem(&w[1], &p_from, 4);
+ copymem(&w[5], &p_to, 4);
+ copymem(&w[PROTO_SIZE], p_data, p_data_size);
+
+ return out;
+}
+
+void WebSocketMultiplayerPeer::_send_add(int32_t p_peer_id) {
+
+ // First of all, confirm the ID!
+ _send_sys(get_peer(p_peer_id), SYS_ID, p_peer_id);
+
+ // Then send the server peer (which will trigger connection_succeded in client)
+ _send_sys(get_peer(p_peer_id), SYS_ADD, 1);
+
+ for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) {
+ uint32_t id = E->key();
+ if (p_peer_id == id)
+ continue; // Skip the newwly added peer (already confirmed)
+
+ // Send new peer to others
+ _send_sys(get_peer(id), SYS_ADD, p_peer_id);
+ // Send others to new peer
+ _send_sys(get_peer(p_peer_id), SYS_ADD, id);
+ }
+}
+
+void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) {
+ for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) {
+ uint32_t id = E->key();
+ if (p_peer_id != id)
+ _send_sys(get_peer(id), SYS_DEL, p_peer_id);
+ }
+}
+
+void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size) {
+ Packet packet;
+ packet.data = (uint8_t *)memalloc(p_data_size);
+ packet.size = p_data_size;
+ packet.source = p_source;
+ packet.destination = p_dest;
+ copymem(packet.data, &p_data[PROTO_SIZE], p_data_size);
+ _incoming_packets.push_back(packet);
+ emit_signal("peer_packet", p_source);
+}
+
+Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size) {
+ if (p_to == 1) {
+
+ return OK; // Will not send to self
+
+ } else if (p_to == 0) {
+
+ for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) {
+ if (E->key() != p_from)
+ E->get()->put_packet(p_buffer, p_buffer_size);
+ }
+ return OK; // Sent to all but sender
+
+ } else if (p_to < 0) {
+
+ for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) {
+ if (E->key() != p_from && E->key() != -p_to)
+ E->get()->put_packet(p_buffer, p_buffer_size);
+ }
+ return OK; // Sent to all but sender and excluded
+
+ } else {
+
+ ERR_FAIL_COND_V(p_to == p_from, FAILED);
+
+ return get_peer(p_to)->put_packet(p_buffer, p_buffer_size); // Sending to specific peer
+ }
+}
+
+void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id) {
+
+ ERR_FAIL_COND(!p_peer.is_valid());
+
+ const uint8_t *in_buffer;
+ int size = 0;
+ int data_size = 0;
+
+ Error err = p_peer->get_packet(&in_buffer, size);
+
+ ERR_FAIL_COND(err != OK);
+ ERR_FAIL_COND(size < PROTO_SIZE);
+
+ data_size = size - PROTO_SIZE;
+
+ uint8_t type = 0;
+ int32_t from = 0;
+ int32_t to = 0;
+ copymem(&type, in_buffer, 1);
+ copymem(&from, &in_buffer[1], 4);
+ copymem(&to, &in_buffer[5], 4);
+
+ if (is_server()) { // Server can resend
+
+ ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages
+ ERR_FAIL_COND(from != p_peer_id); // Someone is cheating
+
+ _server_relay(from, to, in_buffer, size); // Relay if needed
+
+ if (to == 1) { // This is for the server
+
+ _store_pkt(from, to, in_buffer, data_size);
+
+ } else if (to == 0) {
+
+ // Broadcast, for us too
+ _store_pkt(from, to, in_buffer, data_size);
+
+ } else if (to < 0) {
+
+ // All but one, for us if not excluded
+ if (_peer_id != -p_peer_id)
+ _store_pkt(from, to, in_buffer, data_size);
+
+ } else {
+
+ // Send to specific peer
+ ERR_FAIL_COND(!_peer_map.has(to));
+ get_peer(to)->put_packet(in_buffer, size);
+ }
+
+ } else {
+
+ if (type == SYS_NONE) { // Payload message
+
+ _store_pkt(from, to, in_buffer, data_size);
+ return;
+ }
+
+ // System message
+ ERR_FAIL_COND(data_size < 4);
+ int id = 0;
+ copymem(&id, &in_buffer[PROTO_SIZE], 4);
+
+ switch (type) {
+
+ case SYS_ADD: // Add peer
+ _peer_map[id] = Ref<WebSocketPeer>();
+ emit_signal("peer_connected", id);
+ if (id == 1) // We just connected to the server
+ emit_signal("connection_succeeded");
+ break;
+
+ case SYS_DEL: // Remove peer
+ _peer_map.erase(id);
+ emit_signal("peer_disconnected", id);
+ break;
+ case SYS_ID: // Helo, server assigned ID
+ _peer_id = id;
+ break;
+ default:
+ ERR_EXPLAIN("Invalid multiplayer message");
+ ERR_FAIL();
+ break;
+ }
+ }
+}
diff --git a/modules/websocket/websocket_multiplayer.h b/modules/websocket/websocket_multiplayer.h
new file mode 100644
index 0000000000..e8e795e97f
--- /dev/null
+++ b/modules/websocket/websocket_multiplayer.h
@@ -0,0 +1,110 @@
+/*************************************************************************/
+/* websocket_multiplayer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef WEBSOCKET_MULTIPLAYER_PEER_H
+#define WEBSOCKET_MULTIPLAYER_PEER_H
+
+#include "core/error_list.h"
+#include "core/io/networked_multiplayer_peer.h"
+#include "core/list.h"
+#include "websocket_peer.h"
+
+class WebSocketMultiplayerPeer : public NetworkedMultiplayerPeer {
+
+ GDCLASS(WebSocketMultiplayerPeer, NetworkedMultiplayerPeer);
+
+private:
+ PoolVector<uint8_t> _make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size);
+ void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size);
+ Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size);
+
+protected:
+ enum {
+ SYS_NONE = 0,
+ SYS_ADD = 1,
+ SYS_DEL = 2,
+ SYS_ID = 3,
+
+ PROTO_SIZE = 9,
+ SYS_PACKET_SIZE = 13,
+ MAX_PACKET_SIZE = 65536 - 14 // 5 websocket, 9 multiplayer
+ };
+
+ struct Packet {
+ int source;
+ int destination;
+ uint8_t *data;
+ uint32_t size;
+ };
+
+ List<Packet> _incoming_packets;
+ Map<int, Ref<WebSocketPeer> > _peer_map;
+ Packet _current_packet;
+
+ bool _is_multiplayer;
+ int _target_peer;
+ int _peer_id;
+ int _refusing;
+
+ static void _bind_methods();
+
+ void _send_add(int32_t p_peer_id);
+ void _send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id);
+ void _send_del(int32_t p_peer_id);
+ int _gen_unique_id() const;
+
+public:
+ /* NetworkedMultiplayerPeer */
+ void set_transfer_mode(TransferMode p_mode);
+ TransferMode get_transfer_mode() const;
+ void set_target_peer(int p_peer_id);
+ int get_packet_peer() const;
+ int get_unique_id() const;
+ virtual bool is_server() const = 0;
+ void set_refuse_new_connections(bool p_enable);
+ bool is_refusing_new_connections() const;
+ virtual ConnectionStatus get_connection_status() const = 0;
+
+ /* PacketPeer */
+ virtual int get_available_packet_count() const;
+ virtual int get_max_packet_size() const;
+ virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size);
+ virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size);
+
+ /* WebSocketPeer */
+ virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const = 0;
+
+ void _process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id);
+ void _clear();
+
+ WebSocketMultiplayerPeer();
+ ~WebSocketMultiplayerPeer();
+};
+
+#endif // WEBSOCKET_MULTIPLAYER_PEER_H
diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp
new file mode 100644
index 0000000000..a6fbb4481b
--- /dev/null
+++ b/modules/websocket/websocket_peer.cpp
@@ -0,0 +1,49 @@
+/*************************************************************************/
+/* websocket_peer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "websocket_peer.h"
+
+GDCINULL(WebSocketPeer);
+
+WebSocketPeer::WebSocketPeer() {
+}
+
+WebSocketPeer::~WebSocketPeer() {
+}
+
+void WebSocketPeer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_write_mode"), &WebSocketPeer::get_write_mode);
+ ClassDB::bind_method(D_METHOD("set_write_mode", "mode"), &WebSocketPeer::set_write_mode);
+ ClassDB::bind_method(D_METHOD("is_connected_to_host"), &WebSocketPeer::is_connected_to_host);
+ ClassDB::bind_method(D_METHOD("was_string_packet"), &WebSocketPeer::was_string_packet);
+ ClassDB::bind_method(D_METHOD("close"), &WebSocketPeer::close);
+
+ BIND_ENUM_CONSTANT(WRITE_MODE_TEXT);
+ BIND_ENUM_CONSTANT(WRITE_MODE_BINARY);
+}
diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h
new file mode 100644
index 0000000000..f4d8ce3e38
--- /dev/null
+++ b/modules/websocket/websocket_peer.h
@@ -0,0 +1,73 @@
+/*************************************************************************/
+/* websocket_peer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef WEBSOCKETPEER_H
+#define WEBSOCKETPEER_H
+
+#include "core/error_list.h"
+#include "core/io/packet_peer.h"
+#include "core/ring_buffer.h"
+#include "websocket_macros.h"
+
+class WebSocketPeer : public PacketPeer {
+
+ GDCLASS(WebSocketPeer, PacketPeer);
+ GDCICLASS(WebSocketPeer);
+
+public:
+ enum WriteMode {
+ WRITE_MODE_TEXT,
+ WRITE_MODE_BINARY,
+ };
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual int get_available_packet_count() const = 0;
+ virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) = 0;
+ virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) = 0;
+ virtual int get_max_packet_size() const = 0;
+
+ virtual WriteMode get_write_mode() const = 0;
+ virtual void set_write_mode(WriteMode p_mode) = 0;
+
+ virtual void close() = 0;
+
+ virtual bool is_connected_to_host() const = 0;
+ virtual IP_Address get_connected_host() const = 0;
+ virtual uint16_t get_connected_port() const = 0;
+ virtual bool was_string_packet() const = 0;
+
+ WebSocketPeer();
+ ~WebSocketPeer();
+};
+
+VARIANT_ENUM_CAST(WebSocketPeer::WriteMode);
+#endif // WEBSOCKETPEER_H
diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp
new file mode 100644
index 0000000000..ba77019f55
--- /dev/null
+++ b/modules/websocket/websocket_server.cpp
@@ -0,0 +1,94 @@
+/*************************************************************************/
+/* websocket_server.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "websocket_server.h"
+
+GDCINULL(WebSocketServer);
+
+WebSocketServer::WebSocketServer() {
+ _peer_id = 1;
+}
+
+WebSocketServer::~WebSocketServer() {
+}
+
+void WebSocketServer::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("is_listening"), &WebSocketServer::is_listening);
+ ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(PoolVector<String>()), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop);
+ ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer);
+
+ ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id")));
+ ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol")));
+ ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id")));
+}
+
+NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const {
+ if (is_listening())
+ return CONNECTION_CONNECTED;
+
+ return CONNECTION_DISCONNECTED;
+};
+
+bool WebSocketServer::is_server() const {
+
+ return true;
+}
+
+void WebSocketServer::_on_peer_packet(int32_t p_peer_id) {
+
+ if (_is_multiplayer) {
+ _process_multiplayer(get_peer(p_peer_id), p_peer_id);
+ } else {
+ emit_signal("data_received", p_peer_id);
+ }
+}
+
+void WebSocketServer::_on_connect(int32_t p_peer_id, String p_protocol) {
+
+ if (_is_multiplayer) {
+ // Send add to clients
+ _send_add(p_peer_id);
+ emit_signal("peer_connected", p_peer_id);
+ } else {
+ emit_signal("client_connected", p_peer_id, p_protocol);
+ }
+}
+
+void WebSocketServer::_on_disconnect(int32_t p_peer_id) {
+
+ if (_is_multiplayer) {
+ // Send delete to clients
+ _send_del(p_peer_id);
+ emit_signal("peer_disconnected", p_peer_id);
+ } else {
+ emit_signal("client_disconnected", p_peer_id);
+ }
+}
diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h
new file mode 100644
index 0000000000..db188811fd
--- /dev/null
+++ b/modules/websocket/websocket_server.h
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* websocket_server.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef WEBSOCKET_H
+#define WEBSOCKET_H
+
+#include "core/reference.h"
+#include "websocket_multiplayer.h"
+#include "websocket_peer.h"
+
+class WebSocketServer : public WebSocketMultiplayerPeer {
+
+ GDCLASS(WebSocketServer, WebSocketMultiplayerPeer);
+ GDCICLASS(WebSocketServer);
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void poll() = 0;
+ virtual Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false) = 0;
+ virtual void stop() = 0;
+ virtual bool is_listening() const = 0;
+ virtual bool has_peer(int p_id) const = 0;
+ virtual Ref<WebSocketPeer> get_peer(int p_id) const = 0;
+ virtual bool is_server() const;
+ ConnectionStatus get_connection_status() const;
+
+ void _on_peer_packet(int32_t p_peer_id);
+ void _on_connect(int32_t p_peer_id, String p_protocol);
+ void _on_disconnect(int32_t p_peer_id);
+
+ WebSocketServer();
+ ~WebSocketServer();
+};
+
+#endif // WEBSOCKET_H