/*
* Copyright 2011 Uwe Krueger.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mandelsoft.mand.srv;
import java.util.HashSet;
import com.mandelsoft.mand.MandelData;
import com.mandelsoft.mand.MandelInfo;
import com.mandelsoft.mand.PixelIterator;
import com.mandelsoft.util.ChangeEvent;
import com.mandelsoft.util.ChangeListener;
import com.mandelsoft.util.StateChangeSupport;
/**
*
* @author Uwe Krüger
*/
public class AreaHandler implements Request {
private MandelData data;
private int sx;
private int sy;
private int nx;
private int ny;
private boolean full;
private boolean recalc;
private Server server;
private PixelIterator pi;
private long mtime;
private boolean log;
public AreaHandler(Server server, boolean recalc, boolean full,
MandelData data, int sx, int sy, int nx, int ny)
{
this.full=full;
this.recalc=recalc;
this.server=server;
this.data=data;
this.sx=sx;
this.sy=sy;
this.nx=nx;
this.ny=ny;
this.mtime=0;
}
private void log(String m)
{
if (log) System.out.println(m);
}
public void setPixelIterator(PixelIterator pi)
{
this.pi=pi;
}
public long getMTime()
{
return mtime;
}
synchronized
public void initiate()
{
startStateSetup();
if (!full) {
startFrameState();
// frame already processed, just regularily continue
// with next state
}
else if (nx*ny<500) {
startCalcRequestState();
calc("full", sx+(full?0:1), sy+(full?0:1),
nx-(full?0:2), ny-(full?0:2));
}
else {
startFrameState();
if (full) {
//up
calc("top ", sx+1, sy, nx-2, 1);
//down
calc("bottom", sx+1, sy+ny-1, nx-2, 1);
// left
calc("left ", sx, sy, 1, ny);
// right
calc("right ", sx+nx-1, sy, 1, ny);
}
}
full=false;
finishStateSetup();
}
private void calc(String msg, int x0, int y0, int dx, int dy)
{
if (!full) {
// correct area not to contain frame that is already processed
if (x0==sx) {
x0++;
dx--;
}
else if (x0+dx==sx+nx) {
dx--;
}
if (y0==sy) {
y0++;
dy--;
}
else if (y0+dy==sy+ny) {
dy--;
}
}
if (dx>0 && dy>0) {
if (dx*dy>500) {
int d;
if (dx>dy) {
d=dx/2;
calc(msg,x0,y0,d,dy);
calc(msg,x0+d,y0,dx-d,dy);
}
else {
d=dy/2;
calc(msg,x0,y0,dx,d);
calc(msg,x0,y0+d,dx,dy-d);
}
}
else initiate(msg,x0,y0,dx,dy);
}
}
private void initiate(String msg, int x0, int y0, int dx, int dy)
{
CalcRequest req=new CalcRequest(data.getInfo(),x0,y0,dx,dy);
if (recalc && !put(req)) {
log("skipped calc "+msg+": "+x0+", "+y0+", "+dx+", "+dy);
return;
}
log("calc "+msg+": "+x0+", "+y0+", "+dx+", "+dy);
addRequest(req);
}
private void initiateSubArea(int x0, int y0, int dx, int dy)
{
if (dx>0 && dy>0) {
log("sub: "+x0+", "+y0+", "+dx+", "+dy);
AreaHandler req=new AreaHandler(server, recalc, false, data,
x0, y0, dx, dy);
addRequest(req);
}
}
private boolean constantFrame()
{ int[][] raster=getRaster();
int it=raster[sy][sx];
if (!equals(raster,it,sx+1, sy, nx-1,1)) return false;
if (!equals(raster,it,sx+1, sy+ny-1, nx-1,1)) return false;
if (!equals(raster,it,sx, sy+1, 1,ny-1)) return false;
if (!equals(raster,it,sx+nx-1,sy+1, 1,ny-1)) return false;
return true;
}
private boolean equals(int[][] raster, int it, int x0, int y0,
int dx, int dy)
{
for (int y=y0; y<y0+dy; y++) {
for (int x=x0; x<x0+dx; x++) {
if (raster[y][x]!=it) return false;
}
}
return true;
}
private void fillFrame()
{
int[][] raster=getRaster();
fillFrame(raster,raster[sy][sx],sx+1,sy+1,nx-2,ny-2);
}
private void fillFrame(int[][] raster, int it, int x0, int y0,
int dx, int dy)
{ int m=it==0?1:0;
int cnt=0;
int mcnt=0;
log("fill "+it+": "+x0+", "+y0+", "+dx+", "+dy);
MandelInfo info=data.getInfo();
for (int y=y0; y<y0+dy; y++) {
for (int x=x0; x<x0+dx; x++) {
raster[y][x]=it;
cnt+=it;
mcnt+=m;
}
}
info.setMCnt(info.getMCnt()+mcnt);
info.setNumIt(info.getNumIt()+cnt);
}
private int[][] getRaster()
{
return data.getRaster().getRaster();
}
/////////////////
// take over of calc request results
private void transfer(CalcRequest req)
{ int nx=req.getNX();
int ny=req.getNY();
int sx=req.getSX();
int sy=req.getSY();
int[][] raster=getRaster();
MandelInfo info=data.getInfo();
for (int x=0; x<nx; x++) {
for (int y=0; y<ny; y++) {
int ax=sx+x;
int ay=sy+y;
raster[ay][ax]=req.getDataRel(x, y);
}
}
if (req.getMaxIt()>info.getMaxIt()) info.setMaxIt(req.getMaxIt());
if (req.getMinIt()<info.getMinIt()) info.setMinIt(req.getMinIt());
info.setMCnt(info.getMCnt()+req.getMCnt());
info.setMCCnt(info.getMCCnt()+req.getCCnt());
info.setNumIt(info.getNumIt()+req.getNumIt());
mtime+=req.getMTime();
}
private boolean put(CalcRequest req)
{ int nx=req.getNX();
int ny=req.getNY();
int sx=req.getSX();
int sy=req.getSY();
MandelInfo info=data.getInfo();
int[][] raster=getRaster();
boolean found=false;
int minit=info.getMinIt();
int maxit=info.getMinIt();
long mcnt=info.getMCnt();
long ccnt=info.getMCCnt();
long numit=info.getNumIt();
//int[] buffer=
req.createData();
for (int x=0; x<nx; x++) {
for (int y=0; y<ny; y++) {
int ax=sx+x;
int ay=sy+y;
int it=raster[ay][ax];
req.setDataRel(x,y,it);
//buffer[req.getIndexRel(x, y)]=it;
if (it==0) {
found=true;
}
numit+=it;
if (it>maxit) maxit=it;
if (it<minit) minit=it;
}
}
if (!found) {
info.setMaxIt(maxit);
info.setMinIt(minit);
info.setMCnt(mcnt);
info.setMCCnt(ccnt);
info.setNumIt(numit);
}
return found;
}
///////////////////////////////////////////////////////////////
// state handling
///////////////////////////////////////////////////////////////
///////////////////
// state diagramm
// +--------calcrequest-----------+
// / / \
// initial -> frame -> divide -> subarea -> done
//
private boolean instatesetup=false;
synchronized private void startStateSetup()
{
instatesetup=true;
}
synchronized private void finishStateSetup()
{
instatesetup=false;
checkFinishState();
}
synchronized private void checkFinishState()
{
if (!instatesetup && requests.isEmpty() && listener!=null)
listener.nextState();
}
// state change
private void startFrameState()
{
listener=new FrameChangeListener();
}
private void startDivideState()
{
listener=new DivideChangeListener();
}
private void startCalcRequestState()
{
listener=new CalcRequestChangeListener();
}
private void startSubAreaState()
{
listener=new SubAreaChangeListener();
}
private void startDoneState()
{
listener=null;
fireChangeEvent();
}
///////////////////////////////////////////////////////////////
// sub requests
///////////////////////////////////////////////////////////////
private HashSet<Request> requests=new HashSet<Request>();
private HandlerChangeListener listener;
private void addRequest(Request req)
{
if (listener==null) throw new IllegalStateException("illegal area state");
req.addChangeListener(listener);
req.setPixelIterator(pi);
requests.add(req);
req.send(server);
}
///////////////////
// base
private abstract class HandlerChangeListener implements ChangeListener {
final public void stateChanged(ChangeEvent e)
{
synchronized (AreaHandler.this) {
Request req=(Request)e.getSource();
requests.remove(req);
requestProcessed(req);
checkFinishState();
}
}
abstract protected void requestProcessed(Request req);
protected void nextState()
{
startDoneState();
}
}
///////////////////
// calc request
private class CalcRequestChangeListener extends HandlerChangeListener {
protected void requestProcessed(Request req)
{
transfer((CalcRequest)req);
}
}
///////////////////
// outer frame
private class FrameChangeListener extends CalcRequestChangeListener {
@Override
protected void nextState()
{ int d;
startStateSetup();
if (constantFrame()) {
startDoneState();
fillFrame();
}
else {
if (nx*ny<500) {
startCalcRequestState();
calc("full", sx+1, sy+1, nx-2, ny-2);
}
else {
startDivideState();
if (nx>ny) { // divide x
d=nx/2;
calc("div x", sx+d, sy+1, 1, ny-2);
}
else { // divide y
d=ny/2;
calc("div y", sx+1, sy+d, nx-2, 1);
}
}
}
finishStateSetup();
}
}
///////////////////
// divide line
private class DivideChangeListener extends CalcRequestChangeListener {
@Override
protected void nextState()
{ int d;
startStateSetup();
startSubAreaState();
if (nx>ny) { // divide x
d=nx/2;
initiateSubArea(sx, sy, d+1, ny);
initiateSubArea(sx+d, sy, nx-d, ny);
}
else { // divide y
d=ny/2;
initiateSubArea(sx, sy, nx, d+1);
initiateSubArea(sx, sy+d, nx, ny-d);
}
finishStateSetup();
}
}
///////////////////
// nested areas
private class SubAreaChangeListener extends HandlerChangeListener {
protected void requestProcessed(Request req)
{
AreaHandler area=(AreaHandler)req;
mtime+=area.getMTime();
}
}
///////////////////////////////////////////////////////////////
// Request
///////////////////////////////////////////////////////////////
public void send(Server server)
{
initiate(); // handled locally
}
private StateChangeSupport listeners=new StateChangeSupport();
public void removeChangeListener(ChangeListener h)
{
listeners.removeChangeListener(h);
}
private void fireChangeEvent()
{
listeners.fireChangeEvent(this);
}
public void addChangeListener(ChangeListener h)
{
listeners.addChangeListener(h);
}
}