Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Univerxel
Univerxel
Commits
c760edb0
Commit
c760edb0
authored
Sep 24, 2020
by
Shu
🍠
Browse files
Single player server
parents
6dfea31e
fd49ef3f
Changes
28
Hide whitespace changes
Inline
Side-by-side
TODO.md
View file @
c760edb0
...
...
@@ -11,16 +11,17 @@
-
[ ] Chat
-
[~] Authentication
-
[
] Compression
-
[
x
] Compression
-
[ ] Encryption
-
[x] Embedded
-
[ ] Standalone
## Hello world
-
[
] Map stream
-
[
~
] Map stream
-
[ ] Contouring service
-
[ ] Edit
-
[~] Edit
-
Local prediction
-
[~] Occlusion Culling
-
[ ] Iterator ray
-
[ ] Cast from chunk center
...
...
src/client/world/Area.hpp
View file @
c760edb0
...
...
@@ -4,18 +4,18 @@
namespace
world
::
client
{
/// Area (aka big group of client::Chunk)
struct
Area
:
public
world
::
Area
{
struct
Area
final
:
public
world
::
Area
{
public:
struct
params
{
voxel_pos
center
;
int
radius
;
std
::
optional
<
double
>
curvature
;
};
Area
(
const
params
&
p
)
:
world
::
Area
(
p
.
center
,
p
.
radius
),
curvature
(
p
.
curvature
)
{
}
std
::
optional
<
double
>
getCurvature
()
const
override
{
return
curvature
;
}
void
update
(
const
params
&
p
)
{
assert
(
getChunks
().
getRadius
()
==
p
.
radius
);
center
=
p
.
center
;
curvature
=
p
.
curvature
;
}
private:
std
::
optional
<
double
>
curvature
;
};
...
...
src/client/world/Chunk.hpp
View file @
c760edb0
...
...
@@ -4,7 +4,7 @@
namespace
world
::
client
{
class
Chunk
:
public
EdittableChunk
{
class
Chunk
final
:
public
EdittableChunk
{
public:
Chunk
()
:
world
::
Chunk
(),
EdittableChunk
()
{
}
};
...
...
src/client/world/DistanceUniverse.cpp
deleted
100644 → 0
View file @
6dfea31e
#include "DistantUniverse.hpp"
#include "Area.hpp"
#include "../contouring/Abstract.hpp"
#include "../../core/utils/logger.hpp"
using
namespace
world
::
client
;
DistantUniverse
::
DistantUniverse
(
const
connection
&
ct
,
const
options
&
opt
)
:
Universe
(),
net
::
Client
(
ct
)
{
}
DistantUniverse
::~
DistantUniverse
()
{
}
void
DistantUniverse
::
update
(
voxel_pos
pos
,
float
)
{
const
auto
cur_chunk
=
glm
::
divide
(
pos
);
const
auto
chunkChange
=
cur_chunk
!=
last_chunk
;
last_chunk
=
cur_chunk
;
net
::
Client
::
pull
();
if
(
chunkChange
)
{
//NOTE: unprecise
for
(
const
auto
&
area
:
areas
)
{
const
chunk_pos
diff
=
glm
::
divide
(
pos
-
area
.
second
->
getOffset
().
as_voxel
());
for
(
const
auto
&
chunk
:
area
.
second
->
getChunks
())
{
contouring
->
onNotify
(
std
::
make_pair
(
area
.
first
,
chunk
.
first
),
diff
,
area
.
second
->
getChunks
());
}
}
}
contouring
->
update
(
pos
,
areas
);
}
void
DistantUniverse
::
emit
(
const
action
::
packet
&
action
)
{
if
(
const
auto
move
=
std
::
get_if
<
action
::
Move
>
(
&
action
))
{
send
(
net
::
client_packet_type
::
MOVE
,
move
->
pos
,
net
::
channel_type
::
NOTIFY
);
}
else
{
LOG_W
(
"Bad action "
<<
action
.
index
());
}
}
Universe
::
ray_result
DistantUniverse
::
raycast
(
const
geometry
::
Ray
&
)
const
{
return
std
::
monostate
{};
}
\ No newline at end of file
src/client/world/DistantUniverse.cpp
0 → 100644
View file @
c760edb0
#include "DistantUniverse.hpp"
#include <Tracy.hpp>
#include "Area.hpp"
#include "../contouring/Abstract.hpp"
#include "../../core/world/raycast.hpp"
#include "../../core/utils/logger.hpp"
#include "Chunk.hpp"
using
namespace
world
::
client
;
DistantUniverse
::
DistantUniverse
(
const
connection
&
ct
,
const
options
&
opt
)
:
Universe
(),
peer
(
ct
),
loadDistance
(
opt
.
loadDistance
),
keepDistance
(
opt
.
keepDistance
)
{
}
DistantUniverse
::~
DistantUniverse
()
{
}
void
DistantUniverse
::
update
(
voxel_pos
pos
,
float
deltaTime
)
{
const
auto
cur_chunk
=
glm
::
divide
(
pos
);
const
auto
chunkChange
=
cur_chunk
!=
last_chunk
;
last_chunk
=
cur_chunk
;
pullNetwork
();
{
// Update alive areas
ZoneScopedN
(
"World"
);
for
(
auto
&
area
:
areas
)
{
ZoneScopedN
(
"Area"
);
const
bool
chunkChangeArea
=
(
false
&&
area
.
second
->
move
(
glm
::
vec3
(
deltaTime
)))
||
chunkChange
;
// TODO: area.velocity
const
chunk_pos
diff
=
glm
::
divide
(
pos
-
area
.
second
->
getOffset
().
as_voxel
());
auto
&
chunks
=
area
.
second
->
setChunks
();
if
(
glm
::
length2
(
diff
)
<=
glm
::
pow2
(
keepDistance
+
area
.
second
->
getChunks
().
getRadius
()))
{
ZoneScopedN
(
"Alive"
);
auto
it_c
=
chunks
.
begin
();
while
(
it_c
!=
chunks
.
end
())
{
if
(
glm
::
length2
(
diff
-
it_c
->
first
)
>
glm
::
pow2
(
keepDistance
))
{
it_c
=
chunks
.
erase
(
it_c
);
}
else
{
if
(
const
auto
neighbors
=
std
::
dynamic_pointer_cast
<
world
::
client
::
EdittableChunk
>
(
it_c
->
second
)
->
update
(
deltaTime
,
true
/*FIXME: rnd from contouring*/
))
{
contouring
->
onUpdate
(
std
::
make_pair
(
area
.
first
,
it_c
->
first
),
diff
,
chunks
,
neighbors
.
value
());
}
else
if
(
chunkChangeArea
)
{
contouring
->
onNotify
(
std
::
make_pair
(
area
.
first
,
it_c
->
first
),
diff
,
chunks
);
}
++
it_c
;
}
}
}
}
}
contouring
->
update
(
pos
,
areas
);
}
void
DistantUniverse
::
pullNetwork
()
{
ZoneScopedN
(
"Pull"
);
using
namespace
net
;
peer
.
pull
(
[
&
](
packet_t
*
packet
,
channel_type
){
const
server_packet_type
type
=
static_cast
<
server_packet_type
>
(
*
packet
->
data
);
switch
(
type
)
{
case
server_packet_type
::
COMPRESSION
:
{
if
(
dict
.
has_value
())
break
;
dict
.
emplace
(
packet
->
data
+
sizeof
(
server_packet_type
),
packet
->
dataLength
-
sizeof
(
server_packet_type
));
LOG_D
(
"Compression dictionnary loaded"
);
break
;
}
case
server_packet_type
::
AREAS
:
{
auto
reader
=
PacketReader
(
packet
,
true
);
while
(
!
reader
.
isFull
())
{
area_id
id
;
if
(
!
reader
.
read
(
id
))
break
;
world
::
Area
::
params
p
;
if
(
!
reader
.
read
(
p
))
break
;
if
(
auto
it
=
areas
.
find
(
id
);
it
!=
areas
.
end
())
{
std
::
dynamic_pointer_cast
<
Area
>
(
it
->
second
)
->
update
(
p
);
}
else
{
areas
.
emplace
(
id
,
std
::
make_shared
<
Area
>
(
p
));
}
LOG_D
(
"Area "
<<
id
.
index
);
}
break
;
}
case
server_packet_type
::
CHUNK
:
{
ZoneScopedN
(
"Chunk"
);
if
(
!
dict
.
has_value
())
break
;
auto
reader
=
PacketReader
(
packet
,
true
);
area_
<
chunk_pos
>
pos
;
if
(
!
reader
.
read
(
pos
))
break
;
auto
it
=
areas
.
find
(
pos
.
first
);
if
(
it
==
areas
.
end
())
{
LOG_W
(
"Chunk area not found "
<<
pos
.
first
.
index
);
break
;
}
if
(
!
it
->
second
->
getChunks
().
inRange
(
pos
.
second
))
{
LOG_W
(
"Chunk out of area "
<<
pos
.
first
.
index
);
break
;
}
auto
data
=
reader
.
remaning
();
std
::
vector
<
char
>
buffer
;
if
(
auto
err
=
dict
.
value
().
decompress
(
data
,
buffer
))
{
LOG_E
(
"Corrupted chunk packet "
<<
err
.
value
());
break
;
}
vec_istream
idata
(
buffer
);
std
::
istream
iss
(
&
idata
);
auto
ck
=
std
::
make_shared
<
world
::
client
::
EdittableChunk
>
(
iss
);
ck
->
invalidate
(
geometry
::
Faces
::
All
);
auto
ptr
=
std
::
dynamic_pointer_cast
<
world
::
Chunk
>
(
ck
);
auto
&
chunks
=
it
->
second
->
setChunks
();
if
(
auto
it_c
=
chunks
.
find
(
pos
.
second
);
it_c
!=
chunks
.
end
())
{
it_c
->
second
=
ptr
;
}
else
{
chunks
.
emplace
(
pos
.
second
,
ptr
);
}
break
;
}
case
server_packet_type
::
EDITS
:
{
ZoneScopedN
(
"Edits"
);
auto
reader
=
PacketReader
(
packet
,
true
);
area_id
id
;
if
(
!
reader
.
read
(
id
))
break
;
auto
it
=
areas
.
find
(
id
);
if
(
it
==
areas
.
end
())
{
LOG_W
(
"Edit area not found "
<<
id
.
index
);
break
;
}
while
(
!
reader
.
isFull
())
{
chunk_pos
pos
=
chunk_pos
(
INT_MAX
);
reader
.
read
(
pos
);
chunk_voxel_idx
count
=
0
;
reader
.
read
(
count
);
if
(
auto
ptr
=
it
->
second
->
setChunks
().
findInRange
(
pos
))
{
auto
chunk
=
std
::
dynamic_pointer_cast
<
world
::
client
::
EdittableChunk
>
(
ptr
.
value
());
for
(
auto
i
=
0
;
i
<
count
&&
!
reader
.
isFull
();
i
++
)
{
Chunk
::
Edit
edit
;
reader
.
read
(
edit
);
chunk
->
apply
(
edit
);
}
}
else
{
reader
.
skip
(
count
*
sizeof
(
Chunk
::
Edit
));
}
}
break
;
}
default:
LOG_W
(
"Bad packet from server"
);
break
;
}
},
[](
disconnect_reason
){
});
}
void
DistantUniverse
::
emit
(
const
action
::
packet
&
action
)
{
if
(
const
auto
move
=
std
::
get_if
<
action
::
Move
>
(
&
action
))
{
peer
.
send
(
net
::
client_packet_type
::
MOVE
,
move
->
pos
,
net
::
channel_type
::
NOTIFY
);
}
else
if
(
const
auto
fillCube
=
std
::
get_if
<
action
::
FillCube
>
(
&
action
))
{
peer
.
send
(
net
::
client_packet_type
::
FILL_CUBE
,
*
fillCube
,
net
::
channel_type
::
RELIABLE
);
}
else
{
LOG_W
(
"Bad action "
<<
action
.
index
());
}
}
Universe
::
ray_result
DistantUniverse
::
raycast
(
const
geometry
::
Ray
&
ray
)
const
{
return
Raycast
(
ray
,
areas
);
}
\ No newline at end of file
src/client/world/DistantUniverse.hpp
View file @
c760edb0
...
...
@@ -2,12 +2,13 @@
#include "Universe.hpp"
#include "../../core/net/Client.hpp"
#include "../../core/utils/zctx.hpp"
namespace
world
::
client
{
class
Area
;
/// Whole universe container in network client
class
DistantUniverse
final
:
public
Universe
,
net
::
Client
{
class
DistantUniverse
final
:
public
Universe
{
public:
DistantUniverse
(
const
connection
&
,
const
options
&
);
~
DistantUniverse
();
...
...
@@ -18,9 +19,17 @@ namespace world::client {
ray_result
raycast
(
const
geometry
::
Ray
&
)
const
override
;
protected:
void
pullNetwork
();
/// Alive areas containing chunks
area_map
areas
;
net
::
Client
peer
;
std
::
optional
<
zstd
::
read_dict_ctx
>
dict
;
chunk_pos
last_chunk
=
chunk_pos
(
INT_MAX
);
int
loadDistance
;
int
keepDistance
;
};
}
\ No newline at end of file
src/core/data/file.hpp
0 → 100644
View file @
c760edb0
#pragma once
#include <fstream>
#include <vector>
#include "../utils/logger.hpp"
namespace
data
{
class
file_content
:
public
std
::
vector
<
char
>
{
public:
// Read first
file_content
(
const
std
::
vector
<
std
::
string
>&
paths
)
:
std
::
vector
<
char
>
()
{
std
::
ifstream
is
=
[
&
]()
{
for
(
auto
&
path
:
paths
)
{
std
::
ifstream
is
(
path
,
std
::
ios
::
in
|
std
::
ios
::
binary
|
std
::
ios
::
ate
);
if
(
is
.
good
())
{
return
is
;
}
is
.
close
();
}
FATAL
(
"File not found "
<<
paths
.
back
());
}();
const
auto
end
=
is
.
tellg
();
is
.
seekg
(
0
,
std
::
ios
::
beg
);
resize
(
end
-
is
.
tellg
());
is
.
read
(
data
(),
size
());
is
.
close
();
}
};
}
\ No newline at end of file
src/core/net/Client.hpp
View file @
c760edb0
#pragma once
#include "data.hpp"
#include "PacketView.hpp"
#include <Tracy.hpp>
namespace
net
{
...
...
@@ -37,11 +39,12 @@ public:
}
~
Client
()
{
LOG_D
(
"Leaving server"
);
enet_peer_disconnect_now
(
peer
,
1
);
enet_peer_disconnect_now
(
peer
,
(
enet_uint32
)
disconnect_reason
::
QUIT
);
enet_host_destroy
(
host
);
}
void
pull
(
int
delay
=
0
)
{
template
<
typename
D
,
typename
R
>
void
pull
(
R
onData
,
D
onDisconnect
,
int
delay
=
0
)
{
ENetEvent
event
;
while
(
enet_host_service
(
host
,
&
event
,
delay
)
>
0
)
{
switch
(
event
.
type
)
{
...
...
@@ -50,7 +53,9 @@ public:
break
;
case
ENET_EVENT_TYPE_DISCONNECT
:
onDisconnect
(
event
.
data
);
LOG_D
(
"Client disconnected with reason "
<<
event
.
data
);
ready
=
false
;
onDisconnect
((
disconnect_reason
)
event
.
data
);
break
;
case
ENET_EVENT_TYPE_RECEIVE
:
{
...
...
@@ -59,7 +64,7 @@ public:
break
;
}
const
server_packet_type
type
=
static_cast
<
server_packet_type
>
(
*
event
.
packet
->
data
);
if
(
type
<
server_packet_type
::
CHUNK
)
{
if
(
type
<
server_packet_type
::
BROADCASTED
)
{
if
(
event
.
packet
->
dataLength
<
sizeof
(
server_packet_type
)
+
sizeof
(
salt
))
{
LOG_D
(
"Wrong salted packet size"
);
break
;
...
...
@@ -76,14 +81,14 @@ public:
}
salt_t
l
;
memcpy
(
&
l
,
event
.
packet
->
data
+
sizeof
(
server_packet_type
)
+
sizeof
(
salt
),
sizeof
(
l
)
)
;
PacketReader
(
event
.
packet
).
read
(
l
);
salt
^=
l
;
LOG_D
(
"Handshake done"
);
ready
=
true
;
break
;
}
onData
(
event
.
channelID
,
event
.
packet
);
onData
(
event
.
packet
,
(
channel_type
)
event
.
channelID
);
enet_packet_destroy
(
event
.
packet
);
break
;
}
...
...
@@ -92,35 +97,29 @@ public:
break
;
}
}
TracyPlot
(
"CltNetUp"
,
(
int64_t
)
host
->
outgoingBandwidth
);
TracyPlot
(
"CltNetDown"
,
(
int64_t
)
host
->
incomingBandwidth
);
TracyPlot
(
"CltNetRTT"
,
(
int64_t
)
peer
->
roundTripTime
);
}
virtual
void
onDisconnect
(
enet_uint32
reason
)
{
LOG_D
(
"Client disconnected with reason "
<<
reason
);
ready
=
false
;
bool
send
(
packet_t
*
packet
,
channel_type
channel
)
{
return
enet_peer_send
(
peer
,
(
enet_uint8
)
channel
,
packet
)
==
0
;
}
virtual
void
onData
(
enet_uint8
channel
,
ENetPacket
*
packet
)
{
LOG_D
(
"Data from server "
<<
packet
->
dataLength
<<
" on "
<<
channel
);
}
bool
send
(
client_packet_type
type
,
ENetPacket
*
packet
,
channel_type
channel
)
{
assert
(
packet
->
dataLength
>=
HEADER_SIZE
);
if
(
!
ready
)
{
LOG_W
(
"Contacting server before handshake"
);
enet_packet_destroy
(
packet
);
return
false
;
}
memcpy
(
packet
->
data
,
&
type
,
sizeof
(
client_packet_type
));
memcpy
(
packet
->
data
+
sizeof
(
client_packet_type
),
&
salt
,
sizeof
(
salt
));
return
enet_peer_send
(
peer
,
(
enet_uint8
)(
channel
),
packet
)
==
0
;
}
bool
send
(
client_packet_type
type
,
const
void
*
data
,
size_t
size
,
channel_type
channel
,
std
::
optional
<
enet_uint32
>
flags
=
{})
{
auto
packet
=
enet_packet_create
(
NULL
,
HEADER_SIZE
+
size
,
flags
.
value_or
(
channel
==
channel_type
::
RELIABLE
?
ENET_PACKET_FLAG_RELIABLE
:
0
));
memcpy
(
packet
->
data
+
HEADER_SIZE
,
data
,
size
);
return
send
(
type
,
packet
,
channel
);
bool
send
(
client_packet_type
type
,
const
void
*
data
,
size_t
size
,
channel_type
channel
,
std
::
optional
<
enet_uint32
>
flags
=
{})
{
return
send
(
makePacket
(
type
,
data
,
size
,
flags
.
value_or
(
channel
==
channel_type
::
RELIABLE
?
ENET_PACKET_FLAG_RELIABLE
:
0
),
salt
).
get
(),
channel
);
}
template
<
typename
D
>
bool
send
(
client_packet_type
type
,
const
D
&
payload
,
channel_type
channel
,
std
::
optional
<
enet_uint32
>
flags
=
{})
{
return
send
(
type
,
&
payload
,
sizeof
(
payload
),
channel
,
flags
);
bool
send
(
client_packet_type
type
,
const
D
&
data
,
channel_type
channel
,
std
::
optional
<
enet_uint32
>
flags
=
{})
{
return
send
(
type
,
&
data
,
sizeof
(
data
),
channel
,
flags
);
}
static
PacketWriter
makePacket
(
client_packet_type
type
,
const
void
*
data
,
size_t
size
,
enet_uint32
flags
,
salt_t
salt
)
{
auto
packet
=
PacketWriter
(
sizeof
(
client_packet_type
)
+
sizeof
(
salt_t
)
+
size
,
flags
);
packet
.
write
(
type
);
packet
.
write
(
salt
);
if
(
data
!=
nullptr
)
packet
.
write
(
data
,
size
);
return
packet
;
}
constexpr
bool
isReady
()
const
{
return
ready
;
}
...
...
src/core/net/PacketView.hpp
0 → 100644
View file @
c760edb0
#pragma once
#include "data.hpp"
struct
vec_istream
:
std
::
streambuf
{
vec_istream
(
std
::
vector
<
char
>
&
vec
)
{
this
->
setg
(
&
vec
[
0
],
&
vec
[
0
],
&
vec
[
0
]
+
vec
.
size
());
}
};
namespace
net
{
class
PacketWriter
final
{
public:
PacketWriter
(
size_t
size
,
enet_uint32
flags
)
:
p
(
enet_packet_create
(
NULL
,
size
,
flags
))
{
}
void
write
(
const
void
*
data
,
size_t
size
)
{
assert
(
i
+
size
<=
p
->
dataLength
);
memcpy
(
p
->
data
+
i
,
data
,
size
);
i
+=
size
;
}
template
<
typename
D
>
void
write
(
const
D
&
d
)
{
write
(
&
d
,
sizeof
(
d
));
}
bool
isFull
()
const
{
return
i
>=
p
->
dataLength
;
}
packet_t
*
get
()
{
return
p
;
}
private:
packet_t
*
p
;
size_t
i
=
0
;
};
struct
data_ref
{
void
*
d
;
size_t
s
;
void
*
data
()
{
return
d
;
}
const
void
*
data
()
const
{
return
d
;
}
size_t
size
()
const
{
return
s
;
}
};
class
PacketReader
final
{
public:
PacketReader
(
packet_t
*
ptr
,
size_t
offset
)
:
p
(
ptr
),
i
(
offset
)
{
}
PacketReader
(
packet_t
*
ptr
,
bool
broadcasted
=
false
)
:
PacketReader
(
ptr
,
sizeof
(
enet_uint8
)
+
(
broadcasted
?
0
:
sizeof
(
salt_t
)))
{
}
template
<
typename
D
>
const
D
*
read
()
{
if
(
i
+
sizeof
(
D
)
>
p
->
dataLength
)
return
nullptr
;
auto
ptr
=
(
const
D
*
)(
p
->
data
+
i
);
i
+=
sizeof
(
D
);
return
ptr
;
}
template
<
typename
D
>
bool
read
(
D
&
out
)
{
const
auto
ptr
=
read
<
D
>
();
if
(
ptr
==
nullptr
)
return
false
;
out
=
*
ptr
;
return
true
;
}
bool
skip
(
size_t
size
)
{
i
+=
size
;
return
i
<
p
->
dataLength
;
}
data_ref
remaning
()
{
auto
res
=
data_ref
{
p
->
data
+
i
,
p
->
dataLength
-
i
};
i
=
p
->
dataLength
;
return
res
;
}
bool
isFull
()
const
{
return
i
>=
p
->
dataLength
;
}
packet_t
*
get
()
{
return
p
;
}
private:
packet_t
*
p
;
size_t
i
=
0
;
};
}
\ No newline at end of file
src/core/net/Server.hpp
View file @
c760edb0
#pragma once
#include "data.hpp"
#include "PacketView.hpp"
namespace
net
{
...
...
@@ -25,7 +26,8 @@ public:
enet_host_destroy
(
host
);
}
void
pull
(
int
delay
=
0
)
{
template
<
typename
C
,
typename
D
,
typename
R
>
void
pull
(
C
onConnect
,
D
onDisconnect
,
R
onData
,
int
delay
=
0
)
{
ENetEvent
event
;
while
(
enet_host_service
(
host
,
&
event
,
delay
)
>
0
)
{
switch
(
event
.
type
)
{
...
...
@@ -34,23 +36,11 @@ public:
break
;
case
ENET_EVENT_TYPE_DISCONNECT
:
onDisconnect
(
event
.
peer
,
event
.
data
);
onDisconnect
(
event
.
peer
,
(
disconnect_reason
)
event
.
data
);
break
;
case
ENET_EVENT_TYPE_RECEIVE
:
{
if
(
event
.
packet
->
dataLength
<
sizeof
(
client_packet_type
)
+
sizeof
(
salt_t
))
{
LOG_D
(
"Empty packet from "
<<
event
.
peer
->
address
.
host
<<
":"
<<
event
.
peer
->
address
.
port
);
break
;
}
if
(
memcmp
(
event
.
peer
->
data
,
event
.
packet
->
data
+
sizeof
(
client_packet_type
),
sizeof
(
salt_t
))
!=
0
)
{
LOG_D
(
"Wrong salt from "
<<
event
.
peer
->
address
);
salt_t
l
;
memcpy
(
&
l
,
event
.
packet
->
data
+
sizeof
(
client_packet_type
),
sizeof
(
l
));
memcpy
(
&
l
,
event
.
peer
->
data
,
sizeof
(
l
));
enet_peer_disconnect
(
event
.
peer
,
15
);
break
;
}
onData
(
event
.
peer
,
event
.
packet
,
event
.
channelID
);
onData
(
event
.
peer
,
event
.
packet
,
(
channel_type
)
event
.
channelID
);
enet_packet_destroy
(
event
.
packet
);
break
;
}
...
...