%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 场景进程实例入口模块
%%%
%%% call_to_scene与cast_to_scene，默认都不应该主动去触发创建场景，
%%% 确保在合适的时候才主动调用相关接口创建场景。
%%% 具体参考call_to_scene相关注释说明
%%% @end
%%% -------------------------------------------------------
-module(mod_scene_agent).
-behaviour(gen_server).
-export([
        start_scene/2,
        get_scene_pid/1,
        get_scene_pid/2,
        send_to_scene/4,
        send_to_scene/3,
        send_to_area_scene/6,
        % join/2,
        leave/1,
        revive/4,
        move/8,
        update/2,
        update/4,
        % send_scene_info_to_uid/6,
        dispatch_dungeon_event/4,
        send_number_info/4,
        send_user_info/4,
        close_scene/1,
        close_scene/2,
        clear_scene/2,
        clear_scene/3,
        apply_call/5,
        apply_call_with_scene/5,
        apply_cast/5,
        apply_cast_scene_user/6,
        apply_cast_with_scene/5,
        load_scene/1,
        handle_event/2,
        call_to_scene/3,
        cast_to_scene/3,
        cast_to_scene_with_check/3,
        sync_scene_user_num/4,
        get_alive_pid/2

        ,do_notice_roles_erase_scene/2
        %% 注意：本接口一般外部不需要使用，仅查询场景进程pid，不尝试创建进程
        ,find_scene_pid/2
        ,erase_scene_pid/2
        ,load_npc/2
        ,change_dance/5
    ]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%% only for debug by gm, don't call anywhere
-export([
        gm_reload_scene_object/3
        ,gm_get_scene_num/2
    ]).
%% only for internal call, don't call outside
-export([
        load_scene_object/4
        ,notice_roles_erase_scene/2
    ]).
-include("server.hrl").
-include("scene.hrl").
-include("battle.hrl").
-include("common.hrl").
-include("predefine.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").
-include("title.hrl").
-include("designation.hrl").
-include("skill.hrl").
-include("figure.hrl").
-include("def_fun.hrl").
-include("dungeon_multi.hrl").

%%%===================================================================
%%% API
%%%===================================================================

%% 检测获取场景pid（如果场景未启动，会尝试启动）
get_scene_pid(Id) ->
    get_scene_pid(Id, 0).

get_scene_pid(Id, PoolId) ->
    case get({get_scene_pid, Id, PoolId}) of
        ScenePid when is_pid(ScenePid) -> ScenePid;
        undefined ->
            case data_scene:get(Id) of
                #ets_scene{type = SceneType, cls_type=?SCENE_CLS_TYPE_CENTER} ->
                    case config:get_cls_type() of
                        1 -> get_scene_pid_global(Id, PoolId, SceneType);
                        _ -> clusters
                    end;
                #ets_scene{type = SceneType} ->
                    get_scene_pid_global(Id, PoolId, SceneType);
                _ -> undefined
            end
    end.

%% 查询场景pid接口（仅查询不创建进程）
%%
%% 这里独立一套接口（相对于get_scene_pid），
%% 最初是为了避免因close不存在的场景时反而创建了进程，情形如下：
%%   1. 如果调用get_scene_pid，会在无场景时自动创建场景进程
%%   2. 创建场景进程时，因为load怪物是异步过程，会通知到怪物创建器，怪物是异步尝试创建的
%%   3. 在场景进程收到结束消息后，刚创建的进程结束掉了
%%   4. load怪物此时才真实创建，会调用get_scene_pid，导致又创建了新的场景进程
%% 后来调整为大多数场合不需要主动触发创建场景，
%% 仅在有必要的时候，需要显式调用get_scene_pid去检查创建场景
%% ** 在旧项目中，此接口名为get_scene_pid_untry_create **
find_scene_pid(Id, PoolId) ->
    case get({get_scene_pid, Id, PoolId}) of
        ScenePid when is_pid(ScenePid) -> ScenePid;
        undefined ->
            case data_scene:get(Id) of
                #ets_scene{cls_type = ?SCENE_CLS_TYPE_CENTER} ->
                    case config:get_cls_type() of
                        1 ->
                            whereis_scene_pid(Id, PoolId);
                        _ ->    % 游戏节点会返回clusters，供识别是否需要向跨服发送请求
                            clusters
                    end;
                _ ->
                    whereis_scene_pid(Id, PoolId)
            end
    end.

%% 获取场景进程pid
%% @reutrn pid() || false
get_alive_pid(Id, PoolId) ->
    SceneProcessName = misc:scene_process_name(Id, PoolId),
    Pid = get_process_pid(SceneProcessName),
    case misc:is_process_alive(Pid) of
        true  -> Pid;
        false -> false
    end.

%% 通过场景调用函数 - call -> false|Res
apply_call(Sid, PoolId, Module, Method, Args) ->
    call_to_scene(Sid, PoolId, {'apply_call', Module, Method, Args}).
apply_call_with_scene(Sid, PoolId, Module, Method, Args) ->
    call_to_scene(Sid, PoolId, {'apply_call_with_scene', Module, Method, Args}).

%% 通过场景调用函数 - cast
apply_cast(Sid, PoolId, Module, Method, Args) ->
    cast_to_scene(Sid, PoolId, {'apply_cast', Module, Method, Args}).

%% 通过场景调用函数 - cast
%% 调用Method时，最后会带有state参数
apply_cast_with_scene(Sid, PoolId, Module, Method, Args) ->
    cast_to_scene(Sid, PoolId, {'apply_cast_with_scene', Module, Method, Args}).

%% 让场景内所有玩家执行 方法
apply_cast_scene_user(Sid, PoolId, CopyId, Module, Method, Args) ->
    cast_to_scene(Sid, PoolId, {'apply_cast_scene_user', CopyId, Module, Method, Args}).

%% 给场景所有玩家发送信息
send_to_scene(Sid, PoolId, CopyId, Bin) ->
    cast_to_scene(Sid, PoolId, {'send_to_scene', CopyId, Bin}).

%% 给场景所有玩家发送信息
send_to_scene(Sid, PoolId, Bin) ->
    cast_to_scene(Sid, PoolId, {'send_to_scene', Bin}).

%% 给场景九宫格玩家发送信息
send_to_area_scene(Sid, PoolId, CopyId, X, Y, Bin) ->
    cast_to_scene(Sid, PoolId, {'send_to_area_scene', CopyId, X, Y, Bin}).

% join(PS) ->
%     EtsSceneUser = trans_join_data(PS),
%     cast_to_scene(EtsSceneUser#ets_scene_user.scene, EtsSceneUser#ets_scene_user.scene_pool_id, {'join', EtsSceneUser}).

%% 12002加载场景
load_scene(Status = #player_status{id = RoleId,
        scene = SceneId, scene_pool_id = PoolId, copy_id = CopyId
    }) ->
    %% 更新公共线的场景
    mod_chat_agent:update(RoleId, [
            {scene, SceneId},
            {scene_pool_id, PoolId},
            {copy_id, CopyId}
        ]),
    EtsSceneUser = trans_join_data(Status),
    cast_to_scene_with_check(SceneId, PoolId, {'load_scene', EtsSceneUser}).

%% 玩家离开场景
leave(PS) ->
    cast_to_scene(PS#player_status.scene, PS#player_status.scene_pool_id, {'leave', PS#player_status.id}).

%% 玩家原地复活
revive(PS, TriggerSkill, BattleAttr, KilledPos)->
    cast_to_scene(
        PS#player_status.scene, PS#player_status.scene_pool_id,
        {'revive', PS#player_status.copy_id, PS#player_status.id, TriggerSkill, BattleAttr, KilledPos}
    ).

%%移动
move(X, Y, Param1, Param2, MotionType, Param3, Param4, PS) ->
    #player_status{id=Id, scene=SceneId, scene_pool_id=PoolId, copy_id=CopyId}=PS,
    cast_to_scene(SceneId, PoolId, {'move', [CopyId, X, Y, MotionType, Param1, Param2, Param3, Param4, Id]}).

% %% 给指定用户发送场景信息
% send_scene_info_to_uid(Key, Sid, PoolId, CopyId, X, Y) ->
%     cast_to_scene(Sid, PoolId, {'send_scene_info_to_uid', Key, CopyId, X, Y}).

%% 派发副本事件
dispatch_dungeon_event(Sid, PoolId, RoleId, EventTypeId) ->
    cast_to_scene(Sid, PoolId, {'dispatch_dungeon_event', RoleId, EventTypeId}).

%% 发送场景人数
send_number_info(SceneId, ScenePoolId, CopyId, Sid) ->
    cast_to_scene(SceneId, ScenePoolId, {'send_number_info', CopyId, Sid}).

%% 发送场景人物简要信息
send_user_info(SceneId, ScenePoolId, CopyId, Sid) ->
    cast_to_scene(SceneId, ScenePoolId, {'send_user_info', CopyId, Sid}).


change_dance(SceneId, ScenePoolId, CopyId, RoleId, DanceType) ->
    cast_to_scene(SceneId, ScenePoolId, {'change_dance', CopyId, RoleId, DanceType}).


%% 关闭指定的场景！！！
%% 场景关闭后，会通知所有玩家进程清除进程字典中的场景进程PID，但是如果有其他进程保存了该场景进程PID，需要注意清除
close_scene(SceneId) ->
    ScenePools = scene_mgr:get_scene_pool_ids(SceneId),
    [
        close_scene(SceneId, PoolId) || PoolId <- ScenePools
    ].
close_scene(SceneId, PoolId) ->
    case find_scene_pid(SceneId, PoolId) of
        undefined ->
            ignore;
        clusters  ->
            clusters_node:apply_cast(?MODULE, close_scene, [SceneId, PoolId]);
        Pid       ->
            gen_server:cast(Pid, {'close_scene'}),
            erase_scene_pid(SceneId, PoolId)
    end.

%% 清理所有场景数据
clear_scene(Sid, PoolId) ->
    cast_to_scene(Sid, PoolId, {'clear_scene'}).

%% 清理指定场景的房间数据(怪物):通用清理房间
clear_scene(Sid, PoolId, CopyId) ->
    lib_mon:clear_scene_mon(Sid, PoolId, CopyId, 1).

%% 更新场景数据
update(#player_status{scene = SceneId, scene_pool_id=PoolId, id = Id}, KeyValueList) ->
    cast_to_scene(SceneId, PoolId, {'update', Id, KeyValueList}).

update(SceneId, PoolId, Id, KeyValueList) ->
    cast_to_scene(SceneId, PoolId, {'update', Id, KeyValueList}).

%% 主城和野外同步场景房间人数
sync_scene_user_num(SceneId, PoolId, CopyId, Value)->
    cast_to_scene(SceneId, PoolId, {'sync_scene_user_num', SceneId, PoolId, CopyId, Value}).

handle_event(PS, #event_callback{type_id = ?EVENT_LV_UP}) ->
    %% 更新场景服务器
    mod_scene_agent:update(PS, [
            {lv, PS#player_status.figure#figure.lv},
            {battle_attr, PS#player_status.battle_attr}
        ]),
    {ok, PS};
handle_event(PS, #event_callback{}) ->
    {ok, PS}.

%% ------------------------------------------------------------
%% call_to_scene与cast_to_scene，默认都不应该主动去触发创建场景。
%% 如果默认主动触发创建场景，则较难区分不应该主动触发的场合，
%% 容易引起混乱，在很多情况下场景不应该重新创建的也创建起来了。
%%
%% 在有需要触发创建场景的场合，
%% 应显式地调用get_scene_pid，以便提前创建场景进程，主要包含以下场合：
%%   功能系统调用创建怪物操作
%%   功能系统尝试获取场景中的怪物信息
%%   玩法不主动创建怪物的情况，需要由玩家load_scene时触发
%%   其他特定需求
%% 其中，创建怪物或其他调用lib_mon模块接口的操作，已经调用了get_scene_pid，
%% 不需要额外再调用。其他操作需要视实际情况决定是否额外调用get_scene_pid
%% ------------------------------------------------------------

%% call场景 -> false|Res
call_to_scene(SceneId, PoolId, Msg) ->
    case find_scene_pid(SceneId, PoolId) of
        undefined ->
            %% 场景进程不存在，忽略请求
            ?DEBUG("call_to_scene(~w, ~w) pid undefined, msg=~p ", [SceneId, PoolId, Msg]),
            false;
        clusters ->
            ?ERR("call_to_cluster_scene(~w, ~w) not allow, msg=~p ", [SceneId, PoolId, Msg]),
            % clusters_node:apply_call(?MODULE, call_to_scene, [SceneId, PoolId, Msg]);
            false;
        Pid ->
            try
                gen_server:call(Pid, Msg)
            catch
                ?EXCEPTION(_Class, _Reason, _Stack) ->
                    ?ERR("call_to_scene(~w,~w) error={~p,~p}~n~p", [
                            SceneId, PoolId, _Class, _Reason, ?GET_STACK(_Stack)
                        ]),
                    false
            end
    end.

cast_to_scene(SceneId, PoolId, Msg) ->
    case find_scene_pid(SceneId, PoolId) of
        undefined ->
            %% 场景进程不存在，忽略请求
            ?DEBUG("cast_to_scene(~w, ~w) pid undefined, msg=~p ", [SceneId, PoolId, Msg]),
            false;
        clusters ->
            clusters_node:apply_cast(?MODULE, cast_to_scene, [SceneId, PoolId, Msg]);
        Pid ->
            gen_server:cast(Pid, Msg)
    end.

cast_to_scene_with_check(SceneId, PoolId, Msg) ->
    case get_scene_pid(SceneId, PoolId) of
        undefined ->
            ?ERR("cast_to_scene(~w, ~w) pid undefined, msg=~p ", [SceneId, PoolId, Msg]),
            % tool:back_trace_to_file(),
            false;
        clusters ->
            clusters_node:apply_cast(?MODULE, cast_to_scene_with_check, [SceneId, PoolId, Msg]);
        Pid ->
            gen_server:cast(Pid, Msg)
    end.

%% 执行
do_notice_roles_erase_scene(SceneId, PoolId) ->
    OnlinePids = lib_player:get_online_pids(),
    lists:foreach(fun(Pid) ->
                lib_player:apply_cast(Pid, ?APPLY_CAST, ?MODULE, erase_scene_pid, [SceneId, PoolId])
        end, OnlinePids).

%% 擦除进程字典中记录的场景进程pid
erase_scene_pid(SceneId, PoolId) ->
    erase({get_scene_pid, SceneId, PoolId}).

%% 启动
start_link(Scene, PoolId) ->
    SpawnOpts = [
        {spawn_opt, [
                {message_queue_data, off_heap},
                {priority, high}
            ]
        },
        {hibernate_after, 120000}
    ],
    gen_server:start_link(?MODULE, [Scene, PoolId], SpawnOpts).

%% GM调试专用，清理并重新加载场景中的object
gm_reload_scene_object(SceneId, ScenePoolId, CopyId) ->
    lib_mon:clear_scene_mon(SceneId, ScenePoolId, CopyId, 1),
    apply_cast(SceneId, ScenePoolId, ?MODULE, load_scene_object, [SceneId, ScenePoolId, CopyId, 1]).

%% GM调试专用，获取指定场景分线实际人数数据，与ets表数据印证
gm_get_scene_num(Sid, PoolId) ->
    call_to_scene(Sid, PoolId, {'scene_num'}).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%% Num:场景代理进程个数
%% WorkerId:进行标示
%% Scene:场景配置
init([Scene = #ets_scene{id = SceneId, type = SceneType}, PoolId]) ->
    process_flag(trap_exit, true),
    % process_flag(priority, high),
    % process_flag(message_queue_data, off_heap),
    SceneProcessName = misc:scene_process_name(Scene#ets_scene.id, PoolId),
    set_process_pid(SceneProcessName),
    ScenePid = self(),
    %% spawn(fun () -> timer:sleep(1000), lib_scene_mark:load_mark(SceneId, ScenePid) end),
    %% mod_scene:add_node_scene(SceneId),
    %% mod_scene:copy_scene(SceneId, 0),
    %% catch ?ERR("=============mod_scene_agent(~w, ~w) is creat at node ~w ============", [SceneId, PoolId, node()]),
    %% 启动场景对应的怪物逻辑进程（作为场景进程的子进程，如场景进程结束，应回收该进程）
    {ok, MonMgrPid} = mon_mgr:start_link(SceneId, PoolId),
    lib_scene_agent:put_mon_mgr_pid(MonMgrPid),
    %% 启动完成后再初始化加载场景中的元素
    gen_server:cast(ScenePid, {'apply_cast', ?MODULE, load_scene_object, [SceneId, PoolId, 0, 0]}),
    NewScene = Scene#ets_scene{npc = [], mon = [], jump = [], requirement = [], pool_id = PoolId},
    lib_scene_agent:init_role_count(SceneId, PoolId, SceneType),
    {ok, #scene_state{scene = NewScene, msg_count = 0, mon_mgr_pid = MonMgrPid}}.

terminate(Reason, State = #scene_state{
        scene = #ets_scene{id = Id, pool_id = PoolId}
        ,mon_mgr_pid = MonMgrPid}) ->
    ScenePid = self(),
    IsServerStop =
    case Reason of
        shutdown -> true;
        {shutdown, _} -> true;
        _ -> false  %% 包括主动关闭normal和异常退出{'EXIT', Pid, ErrReason}
    end,
    case IsServerStop of
        true  ->
            %% 正常关服时，不需要做清理（有些ETS表之类已经不存在，清理会报错）
            ignore;
        false ->
            ets:delete(?SCENE_ROLE_COUNT, {Id, PoolId}),
            scene_mgr:scene_terminate(Id, PoolId, ScenePid),
            mon_mgr:stop(MonMgrPid),
            notice_roles_erase_scene(Id, PoolId),
            del_process_pid(misc:scene_process_name(Id, PoolId))
    end,
    {ok, State}.

code_change(_OldVsn, State, _Extra)->
    {ok, State}.

-ifdef(DEV_SERVER).
-define(REDS_DIFF,   1200000).
-define(MEM_DIFF,   12000000).
handle_call(Req, From, Status) ->
    dev_tool:func_checker(
        undefined,
        fun(ReqX, FromX, StatusX) ->
                sub_handle_call(ReqX, FromX, StatusX)
        end,
        [Req, From, Status],
        [{reds_diff, ?REDS_DIFF}, {mem_diff, ?MEM_DIFF}]
    ).
handle_cast(Msg, Status) ->
    dev_tool:func_checker(
        undefined,
        fun(MsgX, StatusX) ->
                sub_handle_cast(MsgX, StatusX)
        end,
        [Msg, Status],
        [{reds_diff, ?REDS_DIFF}, {mem_diff, ?MEM_DIFF}]
    ).
handle_info(Info, Status) ->
    dev_tool:func_checker(
        undefined,
        fun(InfoX, StatusX) ->
                sub_handle_info(InfoX, StatusX)
        end,
        [Info, Status],
        [{reds_diff, ?REDS_DIFF}, {mem_diff, ?MEM_DIFF}]
    ).
-else.
handle_call(Req, From, Status) ->
    sub_handle_call(Req, From, Status).
handle_cast(Msg, Status) ->
    sub_handle_cast(Msg, Status).
handle_info(Info, Status) ->
    sub_handle_info(Info, Status).
-endif.

sub_handle_cast(R, OldState) ->
    case check_msg_len(OldState, cast, R) of
        {true, State} ->
            put(<<"scene_cast">>, [R]),
            case catch mod_scene_agent_cast:handle_cast(R, State) of
                {noreply, NewState} ->
                    {noreply, NewState};
                {stop, Normal, NewState} ->
                    {stop, Normal, NewState};
                Reason ->
                    ?ERR("mod_scene_agent_cast error: ~p, Reason:=~p~n",[R, Reason]),
                    {noreply, State}
            end;
        {false, State} ->
            {noreply, State}
    end.

sub_handle_call(R, From, OldState) ->
    case check_msg_len(OldState, call, R) of
        {true, State} ->
            put(<<"scene_call">>, [R]),
            case catch mod_scene_agent_call:handle_call(R , From, State) of
                {reply, NewFrom, NewState} ->
                    {reply, NewFrom, NewState};
                Reason ->
                    ?ERR("mod_scene_agent_call error: ~p, Reason=~p~n",[R, Reason]),
                    {reply, error, State}
            end;
        {false, State} ->
            {noreply, State}
    end.

sub_handle_info({'EXIT', Pid, Reason}, OldState = #scene_state{mon_mgr_pid = MonMgrPid}) ->
    case MonMgrPid =:= Pid of
        true  ->    %% 怪物管理进程不应该崩溃，无法恢复状态，只能关闭场景
            StopReason = {mon_mgr_exit, MonMgrPid, Reason},
            {stop, {error, StopReason}, OldState};
        false ->
            {noreply, OldState}
    end;

sub_handle_info(R, OldState) ->
    case check_msg_len(OldState, info, R) of
        {true, State} ->
            put(<<"scene_info">>, [R]),
            case catch mod_scene_agent_info:handle_info(R, State) of
                {noreply, NewState} ->
                    {noreply, NewState};
                {stop, Normal, NewState} ->
                    {stop, Normal, NewState};
                Reason ->
                    ?ERR("mod_scene_agent_info error: ~p, Reason:=~p~n",[R, Reason]),
                    {noreply, State}
            end;
        {false, State} ->
            {noreply, State}
    end.

%%%===================================================================
%%% Internal functions
%%%===================================================================

%% -------------------------------------------------------------------
%% 场景初始后加载NPC和怪物
%% -------------------------------------------------------------------

%% 场景初始化，加载场景静态信息
load_scene_object(SceneId, PoolId, CopyId, BroadCast) ->
    #ets_scene{id=Id, npc=Npc, mon=Mon} = Scene = data_scene:get(SceneId),
    load_npc(Npc, Id),
    load_mon(Mon, Scene, PoolId, CopyId, BroadCast),
    ok.

%% 加载NPC
load_npc([[NpcId, X, Y, Anima] | T], SceneId) ->
    case data_npc:get(NpcId) of
        #ets_npc{} = Npc ->
            mod_scene_npc:insert(Npc#ets_npc{id = NpcId, x = X, y = Y, scene = SceneId, anima=Anima});
        _  -> skip
    end,
    load_npc(T, SceneId);
load_npc([H|T], SceneId) ->
    ?ERR("scene npc format error scene_id=~p npc=~p~n", [SceneId, H]),
    load_npc(T, SceneId);
load_npc([], _) -> ok.

%% 加载怪物
load_mon([[MonId, X, Y, Angle, Group] | T], #ets_scene{id = SceneId, type = SceneType} = Scene, PoolId, CopyId, Broadcast) ->
    case data_mon:get(MonId) of
        #mon{kind=Kind, lv=Lv} ->
            case SceneType of
                ?SCENE_TYPE_DUNGEON ->
                    ignore;
                ?SCENE_TYPE_GUILD ->
                    ignore;
                _ ->
                    %% 场景内部调用，所以直接调用mod_scene_object_create模块的create_object_cast
                    %% 而不需要使用lib_mon模块的async_create_mon接口（该接口会再cast消息给本场景进程）
                    BroadCast = case lists:member(SceneType,[?SCENE_TYPE_VOID_FAM]) of
                        true->
                            1;
                        _->
                            0
                    end,
                    CreateMonArgs   = [{group, Group}],
                    mod_scene_object_create:create_object_cast(
                        ?BATTLE_SIGN_MON, MonId, SceneId, PoolId, X, Y, Angle, CopyId, BroadCast, CreateMonArgs
                    )
            end,
            mod_scene_mon:insert(#ets_scene_mon{id = MonId, x = X, y = Y, scene = SceneId, kind = Kind, lv=Lv});
        _ ->
            skip
    end,
    load_mon(T, Scene, PoolId, CopyId, Broadcast);
load_mon([H|T], #ets_scene{id = SceneId} = Scene, PoolId, CopyId, Broadcast) ->
    ?ERR("scene mon format error scene_id=~p mon=~p~n", [SceneId, H]),
    load_mon(T, Scene, PoolId, CopyId, Broadcast);
load_mon([], _, _, _, _) -> ok.

%% -------------------------------------------------------------------
%% 负载保护，降低负载过高时直接崩溃的可能性
%% -------------------------------------------------------------------

%% 检查间隔
-define(CHECK_MSG_INTERVAL, 500).
%% 检查场景进程的消息队列长度
check_msg_len(State = #scene_state{
        msg_count = MsgCount, msg_filter_len = MsgFilterLen,
        scene = #ets_scene{id = _SceneId, pool_id = _PoolId}
    }, MsgType, Msg) ->
    NewMsgCount =
    case MsgCount > 4200000000 of
        true  -> 1;
        false -> MsgCount + 1
    end,
    NewState = State#scene_state{msg_count = NewMsgCount, msg_filter_len = erlang:max(MsgFilterLen - 1, 0)},
    MustBeHandle = is_must_be_handle(MsgType, Msg),
    Ret = MustBeHandle orelse MsgFilterLen =< 0,
    % ?INFO_TTY("scene:~w,~w role_count:~w, msg len:~w, wait ignore:~w", [
    %         _SceneId, _PoolId, lib_scene_agent:get_role_count(),
    %         erlang:process_info(self(), message_queue_len),
    %         MsgFilterLen
    %     ]),
    case NewMsgCount rem ?CHECK_MSG_INTERVAL =:= 0 of
        true  ->
            {message_queue_len, MsgLen} = erlang:process_info(self(), message_queue_len),
            if
                MsgLen >= 800 ->
                    ?INFO("[scene:~w,~w, roleCount:~w] Wait ignore len:~w, old len:~w",
                        [_SceneId, _PoolId, lib_scene_agent:get_role_count(), MsgLen, MsgFilterLen]
                    ),
                    {Ret, NewState#scene_state{msg_filter_len = MsgLen}};
                true ->
                    {Ret, NewState}
            end;
        false ->
            {Ret, NewState}
    end.

%% 是否必须处理的类型 -> boolean().
is_must_be_handle(MsgType, Msg) ->
    case {MsgType, Msg} of
        %% 这部分移动、战斗消息太大量，不处理也不至于引起严重问题，必要的时候过滤掉
        {cast, {'move', _}}                                                 -> false;
        {cast, {'apply_cast', lib_battle, start_battle, _}}                 -> false;
        {cast, {'apply_cast', lib_battle, object_battle_cast, _}}           -> false;
        {cast, {'apply_cast', lib_battle, object_assist_cast, _}}           -> false;
        %% 怪物、AI寻目标处理
        {cast, {'apply_cast', lib_scene_agent, get_trace_info_cast, _}}     -> false;
        {cast, {'apply_cast', lib_scene_agent, get_any_for_trace_cast, _}}  -> false;
        {call, {'apply_call', lib_scene_calc, get_area_for_trace, _}}       -> false;

        %% 一些必须要处理的消息
        {cast, {'load_scene', _}}                                           -> true;
        % {cast, {'join', _}}                                                 -> true;
        % {cast, {'send_scene_info_to_uid', _, _, _, _}}                      -> true;
        {cast, {'leave', _}}                                                -> true;
        {cast, {'apply_cast', mod_scene_object_create, _, _}}               -> true;
        {cast, {'apply_cast', lib_scene_object_agent, _, _}}                -> true;
        {call, {'apply_call', mod_scene_object_create, _, _}}               -> true;
        {call, {'apply_call', lib_scene_object_agent, _, _}}                -> true;

        %% 暂时其他消息先处理，后面再看情况
        _ -> true
    end.

%% -------------------------------------------------------------------
%% 场景启动检测及名称注册
%% -------------------------------------------------------------------

%% 启动场景模块
start_scene(Id, PoolId) ->
    case data_scene:get(Id) of
        []    -> undefined;
        Scene ->
            {ok, NewScenePid} = start_link(Scene, PoolId),
            NewScenePid
    end.

%% 这个调用操作当发现进程不存在时，应由场景管理scene_mgr进程来执行创建（scene_mgr:start_scene/3逻辑也要相应调整）
get_scene_pid_global(Id, PoolId, SceneType) ->
    SceneProcessName = misc:scene_process_name(Id, PoolId),
    ScenePid = case get_process_pid(SceneProcessName) of
        Pid when is_pid(Pid) ->
            case misc:is_process_alive(Pid) of
                true  -> Pid;
                false ->
                    exit(Pid, kill),
                    del_process_pid(SceneProcessName),
                    scene_mgr:start_scene(Id, PoolId, SceneType)
            end;
        _ ->
            scene_mgr:start_scene(Id, PoolId, SceneType)
    end,
    case is_pid(ScenePid) of
        true ->
            put({get_scene_pid, Id, PoolId}, ScenePid),
            ScenePid;
        false ->
            undefined
    end.

%% 获取进程pid
get_process_pid(ProcessName) ->
    case config:get_cls_type() of
        1 ->
            %% 跨服中心启动
            misc:whereis_name(local, ProcessName);
        _ ->
            %% 游戏节点启动
            misc:whereis_name(global, ProcessName)
    end.

%% 获取进程pid
set_process_pid(ProcessName) ->
    case config:get_cls_type() of
        1 ->
            %% 跨服中心启动
            misc:register(local, ProcessName, self());
        _ ->
            %% 游戏节点启动
            misc:register(global, ProcessName, self())
    end.

del_process_pid(ProcessName) ->
    case config:get_cls_type() of
        1 ->
            %% 跨服中心启动
            misc:unregister(local, ProcessName);
        _ ->
            %% 游戏节点启动
            misc:unregister(global, ProcessName)
    end.

%% 通知全世界玩家清理自己记录下的某场景进程pid
notice_roles_erase_scene(SceneId, PoolId) ->
    case config:get_cls_type() of
        1 ->
            clusters_center:apply_to_all_node(
                ?MODULE, do_notice_roles_erase_scene, [SceneId, PoolId]
            );
        _ ->
            do_notice_roles_erase_scene(SceneId, PoolId)
    end.

whereis_scene_pid(Id, PoolId) ->
    SceneProcessName = misc:scene_process_name(Id, PoolId),
    get_process_pid(SceneProcessName).

%% 玩家加入场景
trans_join_data(PS) when is_record(PS, player_status)->
    Node = case data_scene:get(PS#player_status.scene) of
        [] -> none;
        #ets_scene{cls_type=?SCENE_CLS_TYPE_CENTER} -> clusters_node:get_node();
        _ -> none
    end,
    %% 被动技能(各种被动累加的技能列表)
    #status_skill{skill_list = SkillList,
        skill_talent_active_list = SkillTalentActiveList
    } = PS#player_status.skill,
    #role_dun_multi{team_id = MulTeamId, dun_id = DunId} = PS#player_status.dun_multi,
    {MateName, MateTitle, MateLv} = mate_api:role_get_wear_mate(PS),
    GodFighting = god_api:enter_scene(PS),
    SkillPassive = PS#player_status.passive_skills,
    FosterSkills = lib_role_foster:get_foster_passive_skill(PS),
    NucleonSkills = lib_role_nucleon:get_nucleon_passive_skill(PS),
    #ets_scene_user{
        id = PS#player_status.id,
        platform = PS#player_status.platform,
        server_num = PS#player_status.server_num,
        server_name = PS#player_status.server_name,
        server_id = PS#player_status.server_id,
        source = PS#player_status.source,
        node = Node,
        figure = PS#player_status.figure,
        battle_attr = PS#player_status.battle_attr,
        pid = PS#player_status.pid,
        sid = PS#player_status.sid,
        team = PS#player_status.team,
        dun_multi = #role_dun_multi{team_id = MulTeamId, dun_id = DunId},
        scene = PS#player_status.scene,
        scene_pool_id = PS#player_status.scene_pool_id,
        copy_id = PS#player_status.copy_id,
        x = PS#player_status.x,
        y = PS#player_status.y,
        last_load_scene = PS#player_status.last_load_scene,
        last_load_pool = PS#player_status.last_load_pool,
        skill_list = SkillList,
        skill_passive = SkillPassive++FosterSkills++NucleonSkills,
        skill_talent_active_list = SkillTalentActiveList,
        follow_target_xy = PS#player_status.follow_target_xy,
        online = PS#player_status.online,
        quickbar_skills = quickbar_api:quickbar_skills(PS),
        boss_tired = local_boss_api:get_reward_boss_tired(PS),
        unite_buffs = custom_unite_buff:battle_buffs(PS),
        mate_role_id = PS#player_status.mate_role_id,
        act_label = PS#player_status.act_label,
        scene_att_objects = PS#player_status.scene_att_objects,
        can_att_list = PS#player_status.can_att_list,
        cannot_beatt_list = PS#player_status.cannot_beatt_list,
        mate_name = MateName,
        mate_title = MateTitle,
        mate_lv = MateLv,
        badge = lib_guild_badge:get_badge(PS)
        ,scene_motion = motion_energy:to_scene_motion(PS#player_status.role_motion)
        ,partner_battle_attr = lib_role_foster:get_foster_battle_attr(PS, partner_battle_attr)
        ,baby_battle_attr = lib_role_foster:get_foster_battle_attr(PS, baby_battle_attr)
        ,child_battle_attr = lib_child_api:get_child_battle_attr(PS)
        ,god_fighting = GodFighting
        ,deserted_boss_tired = local_boss_api:get_deserted_boss_tired(PS)
    }.

